Webpack4 搭建 Vue 项目

1. 前言

由于 Parcel 打包工具的影响,webpack4 也追求零配置搭建项目。而前阵子出现的 vue-cli 3.0也是基于 webpack4 零配置的思想创建的。对于一些习惯webpack3 的开发者难免有些不习惯。本文就带你绕过 vue-cli,用 webpack4 一步步搭建 vue 项目。

注:(本文讲述的是webpack4基础配置,文章有点长,请耐心看完。或者直接查看项目源码,或者ctrl + w

2. 项目搭建

  1. 创建 createVue 文件夹,进入该文件夹, npm init 初始化项目
  2. 安装 webpack 四件套

npm i webpack webpack-cli webpack-dev-server webpack-merge --save-dev

// 当前我使用版本
"webpack": "^4.16.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5", // 开发服务器
"webpack-merge": "^4.1.4" // webpack 配置合并
  1. 创建相应文件
createVue
  |--dist
  |--build
      |--webpack.prod.js
      |--webpack.dev.js
      |--webpack.base.js
  |--src
      |--index.js
      |--app.vue
  |--index.html
// webpack.base.js
// 存放 dev 和 prod 通用配置
const webpack = require('webpack');
const path = require("path");
module.exports = {
  entry: './src/index.js', //入口
  module: {
    rules: []
  },
  plugins: [
    // 解决vender后面的hash每次都改变
    new webpack.HashedModuleIdsPlugin(),
  ],// 插件
};
// webpack.dev.js
// 存放 dev 配置
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const path = require('path');

module.exports = merge(common, {
  devtool: 'inline-source-map',
  devServer: { // 开发服务器
    contentBase: '../dist'
  },
  output: { // 输出
    filename: 'js/[name].[hash].js', // 每次保存 hash 都变化
    path: path.resolve(__dirname, '../dist')
  },
  module: {},
  mode: 'development',
});
// webpack.prod.js
// 存放 prod 配置
const path = require('path');
// 合并配置文件
const merge = require('webpack-merge');
const common = require('./webpack.base.js');

module.exports = merge(common, {
  module: {},
  plugins: [],
  mode: 'production',
  output: {
    filename: 'js/[name].[contenthash].js', //contenthash 若文件内容无变化,则contenthash 名称不变
    path: path.resolve(__dirname, '../dist')
  },
});

webpack4 增加了 mode 属性,设置为 development / production,以下是默认配置

development:

process.env.NODE_ENV 的值设为 development
默认开启以下插件,充分利用了持久化缓存。参考基于 webpack 的持久化缓存方案

NamedChunksPlugin :以名称固化 chunk id
NamedModulesPlugin :以名称固化 module id

production:

process.env.NODE_ENV 的值设为 production
默认开启以下插件,其中 SideEffectsFlagPlugin 和 UglifyJsPlugin 用于 tree-shaking


FlagDependencyUsagePlugin :编译时标记依赖
FlagIncludedChunksPlugin :标记子chunks,防子chunks多次加载
ModuleConcatenationPlugin :作用域提升(scope hosting),预编译功能,提升或者预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度
NoEmitOnErrorsPlugin :在输出阶段时,遇到编译错误跳过
OccurrenceOrderPlugin :给经常使用的ids更短的值
SideEffectsFlagPlugin :识别 package.json 或者 module.rules 的 sideEffects 标志(纯的 ES2015 模块),安全地删除未用到的 export 导出
UglifyJsPlugin :删除未引用代码,并压缩
// index.js
// 需 npm i vue --save
import Vue from 'vue';
import App from './App.vue'
import './index.scss'
new Vue({
  el: '#app',
  render: h => h(App),
});
复制代码
<!-- app.vue -->
<template>
  <div id="app">
    hello world
  </div>
</template>

<script>
export default {
  name: 'app'
}
</script>

<style scoped>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  transform: rotate(0deg);
}
</style>

复制代码
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Suporka Vue App</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
复制代码
  1. 安装 vue 核心解析插件

npm i vue-loader vue-template-compiler --save-dev

// 当前我使用版本
"vue-loader": "^15.2.6",
"vue-template-compiler": "^2.5.17",

由于 vue 的解析在 dev 和 prod 中均需使用,因此归入基本配置 base

// webpack.base.js

// ...省略号
// vue-loader 插件
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
  //...省略号
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件来施展魔法
    new VueLoaderPlugin(),
  ]
};
  1. 安装 html 模板解析插件

npm i html-webpack-plugin --save-dev

// 当前版本 
"html-webpack-plugin": "^3.2.0"

html 解析也属于基本配置,归入 base

// webpack.base.js

// ...省略号
// html插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  //...省略号
  plugins: [
    //...省略号
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../index.html'),
    }),
  ]
};
  1. 创建 npm 命令
"scripts": {
  "start": "webpack-dev-server --hot --open --config build/webpack.dev.js",
  "build": "webpack --config build/webpack.prod.js"
},

--hot 模块热替换

--open 开启本地服务器

此时 npm start,项目可正常运行

3. 功能拓展

  1. 添加 loader
  • CSS loader (包括前处理和后处理)

CSS 基础 loader

"css-loader": "^1.0.0",
"style-loader": "^0.21.0",

CSS 前处理 less 两件套

"less": "^3.8.0",
"less-loader": "^4.1.0",

CSS 前处理 sass 两件套

"node-sass": "^4.9.2",
"sass-loader": "^7.1.0",

CSS 后处理 postcss 两件套

"postcss-loader": "^2.1.6",
"autoprefixer": "^9.1.0",

并在根文件夹创建 postcss.config.js 文件

// postcss.config.js
// 自动添加css兼容属性
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

安装以上依赖,在 base 文件中加入一下 loader 代码

// webpack.base.js

// ...省略号
rules: [
  {
    test: /\.(sa|sc|c)ss$/,
    use: [
      'style-loader',
      'css-loader',
      'postcss-loader',
      'sass-loader',
    ],
  },
  {
    test: /\.less$/,
    use: [
      'style-loader',
      'css-loader',
      'postcss-loader',
      'less-loader',
    ],
  },
]
  • 图片 loader

解析图片,字体等都是用 file-loader,安装npm i file-loader --save-dev

base 文件加入配置

// webpack.base.js

// ...省略号
rules: [
  // ...省略号
  {
    test: /\.(png|svg|jpg|gif)$/,
    use: [
      {
        loader: 'file-loader',
        options: {
          limit: 5000,
          // 分离图片至imgs文件夹
          name: "imgs/[name].[ext]",
        }
      },
    ]
  },
]

4. 打包优化

  1. 解决每次重新打包,dist 文件夹文件未清除
  • 安装 clean-webpack-plugin 插件
// webpack.prod.js

// 打包之前清除文件
const CleanWebpackPlugin = require('clean-webpack-plugin');
// ...省略号
plugins: [
  new CleanWebpackPlugin(['dist/*'], {
    root: path.resolve(__dirname, '../')
  }),
]
  1. 分离 CSS

webpack4 中使用 mini-css-extract-plugin 插件来分离 css。

  • 安装 mini-css-extract-plugin 插件后
// webpack.prod.js

// 分离CSS插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// ...省略号
plugins: [
  new MiniCssExtractPlugin({
    filename: "css/[name].[hash].css",
    chunkFilename: 'css/[id].[hash].css'
  }),
]

另外,还需将各个 css loader中的style-loader 替换为 MiniCssExtractPlugin

图片压缩使用 image-webpack-loader, 安装后 代码如下:

// webpack.prod.js
// ...省略号
rules: [
  {
    test: /\.(sa|sc|c)ss$/,
    use: [
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          // you can specify a publicPath here
          // by default it use publicPath in webpackOptions.output
          publicPath: '../'
        }
      },
      'css-loader',
      'postcss-loader',
      'sass-loader',
    ],
  },
  {
    test: /\.less$/,
    use: [
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          // you can specify a publicPath here
          // by default it use publicPath in webpackOptions.output
          publicPath: '../'
        }
      },
      'css-loader',
      'postcss-loader',
      'less-loader',
    ],
  },
  {
    test: /\.(png|svg|jpg|gif)$/,
    use: [
      {
        loader: 'file-loader',
        options: {
          limit: 5000,
          name: "imgs/[hash].[ext]",
        }
      },
      // 图片压缩
      {
        loader: 'image-webpack-loader',
        options: {
          //   bypassOnDebug: true,
          mozjpeg: {
            progressive: true,
            quality: 65
          },
          optipng: {
            enabled: false,
          },
          pngquant: {
            quality: '65-90',
            speed: 4
          },
          gifsicle: {
            interlaced: false,
          }
        },
      },
    ]
  },
]
  1. 使用 happypack 多进程加快编译速度

同时也需要安装 babel 两件套

"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"happypack": "^5.0.0",

happypack 开发生产环境都用到,配置归入 base

// webpack.base.js
// 使用happypack
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
// ...省略号
rules: [
  {
    test: /\.js$/,
    //把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
    loader: 'happypack/loader?id=happyBabel',
    //排除node_modules 目录下的文件
    exclude: /node_modules/
  },
]
plugins: [
//...
new HappyPack({
      //用id来标识 happypack处理类文件
      id: "happyBabel",
      //如何处理 用法和loader 的配置一样
      loaders: [
        {
          loader: "babel-loader?cacheDirectory=true"
        }
      ],
      //共享进程池
      threadPool: happyThreadPool,
      //允许 HappyPack 输出日志
      verbose: true
    }),
 ]
  1. 分离不常变化的文件,如 node_modules 下引用的库
// webpack.prod.js
module.exports = merge(common, {
  // ...省略号
  optimization: {
    // 分离chunks
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          name: "vendor",
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          chunks: "initial" // 只打包初始时依赖的第三方
        },
      }
    },
  },
})

如此配置,则打包的 js 文件夹中会多一个 vendor.js

  1. 压缩CSS和JS代码

安装 optimize-css-assets-webpack-plugin 和 uglifyjs-webpack-plugin 插件

// webpack.prod.js
// 压缩CSS和JS代码
// ...省略号
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = merge(common, {
  // ...省略号
  optimization: {
    // ...省略号
    minimizer: [
      // 压缩JS
      new UglifyJsPlugin({
        uglifyOptions: {
          compress: {
            warnings: false, // 去除警告
            drop_debugger: true, // 去除debugger
            drop_console: true // 去除console.log
          },
        },
        cache: true, // 开启缓存
        parallel: true, // 平行压缩
        sourceMap: false // set to true if you want JS source maps
      }),
      // 压缩css
      new OptimizeCSSAssetsPlugin({})
    ]
  },
})

最后,再拓展一个 hash, chunkhash, contenthash 的区别

  • hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值
  • chunkhash和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。
  • contenthash 更细致地根据内容更改,生成对应的哈希值。解决chunkhash 文件中引入的文件名因 chunkhash 变动而变动的问题

项目源码

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊nacos的ConfigDataChangeEvent

nacos-1.1.3/config/src/main/java/com/alibaba/nacos/config/server/utils/event/Eve...

2900
来自专栏MYSQL轻松学

MySQL高可用工具—Orchestrator初识

Orchestrator是一款开源的MySQL复制拓扑管理工具,采用go语言编写,支持MySQL主从复制拓扑关系的调整、支持MySQL主库故障自动切换、手动主从...

16020
来自专栏中二病也要当白帽子

Ethernaut WriteUp 更新到22题 Shop

网上有几篇WP了,但是有的题目短缺,有的不够详细,有的POC,MagicNumber题目更新后是过不了的~~虽说我这里也不可能做到最详细,但是综合起来看的话,应...

17030
来自专栏前端自习课

【JS】382- JavaScript 模块化方案总结

本文包含两部分,第一部分通过简明的描述介绍什么是 CommonJS、AMD、CMD、UMD、ES Module 以及它们的常见用法,第二部分则根据实际问题指出在...

8830
来自专栏岳鹰前端圈

10分钟彻底搞懂前端页面性能监控

前端页面性能是一个非常核心的用户体验指标。本文介绍阿里UC 岳鹰全景监控平台 如何设计一个通用、低侵入性、自动上报的页面性能监控方案。主要采用的是Navigat...

18920
来自专栏sktj

python 回调装饰器

通过使用生成器和协程可以使得回调函数内联在某个函数中。 为了演示说明,假设你有如下所示的一个执行某种计算任务然后调用一个回调函数的函数(参考7.10小节):

10550
来自专栏网站建设、网站制作专栏

javascrip基础:var,let和const区别在哪里

虽然小编我主要工作时后端框架搭建,但空闲时候也经常捣鼓前端的东西,下面就分享一下入门基础知识,老鸟略过,废话也不多话,上代码之前先上概念,先理论后再实践是我一贯...

3500
来自专栏SH的全栈笔记

WebAssembly完全入门——了解wasm的前世今身

接触WebAssembly之后,在google上看了很多资料。感觉对WebAssembly的使用、介绍、意义都说的比较模糊和笼统。感觉看了之后收获没有达到预期,...

15850
来自专栏纯洁的微笑

你知道如何安全正确的关闭线程池吗?

我们知道应用停机时需要释放资源,关闭连接,而对于一些定时任务或者网络请求服务会使用线程池,当应用停机时我们需要正确安全的关闭线程池,如果处理不当,可能造成数据丢...

21430
来自专栏SH的全栈笔记

初探Java类型擦除

本篇博客主要介绍了Java类型擦除的定义,详细的介绍了类型擦除在Java中所出现的场景。

8330

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励