因为 Parcel 打包工具的影响,webpack4 也追求零配置搭建项目。而前阵子出现的 vue-cli 3.0也是基于 webpack4 零配置的思想建立的。对于一些习惯webpack3 的开发者不免有些不习惯。本文就带你绕过 vue-cli,用 webpack4 一步步搭建 vue 项目。javascript
注:(本文讲述的是webpack4基础配置,文章有点长,请耐心看完。或者直接查看项目源码,或者ctrl + w
)css
npm init
初始化项目npm i webpack webpack-cli webpack-dev-server webpack-merge --save-dev
html
// 当前我使用版本 "webpack": "^4.16.3", "webpack-cli": "^3.1.0", "webpack-dev-server": "^3.1.5", // 开发服务器 "webpack-merge": "^4.1.4" // webpack 配置合并
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'); 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,如下是默认配置vue
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>
npm i vue-loader vue-template-compiler --save-dev
java
// 当前我使用版本 "vue-loader": "^15.2.6", "vue-template-compiler": "^2.5.17",
因为 vue 的解析在 dev 和 prod 中均需使用,所以纳入基本配置 basenode
// webpack.base.js // ...省略号 // vue-loader 插件 const VueLoaderPlugin = require('vue-loader/lib/plugin'); module.exports = { //...省略号 module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' } ] }, plugins: [ // 请确保引入这个插件来施展魔法 new VueLoaderPlugin(), ] };
npm i html-webpack-plugin --save-dev
webpack
// 当前版本 "html-webpack-plugin": "^3.2.0"
html 解析也属于基本配置,纳入 basegit
// webpack.base.js // ...省略号 // html插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { //...省略号 plugins: [ //...省略号 new HtmlWebpackPlugin({ template: path.resolve(__dirname, '../index.html'), }), ] };
"scripts": { "start": "webpack-dev-server --hot --open --config build/webpack.dev.js", "build": "webpack --config build/webpack.prod.js" },
--hot 模块热替换github
--open 开启本地服务器web
此时 npm start
,项目可正常运行
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', ], }, ]
解析图片,字体等都是用 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]", } }, ] }, ]
// webpack.prod.js // 打包以前清除文件 const CleanWebpackPlugin = require('clean-webpack-plugin'); // ...省略号 plugins: [ new CleanWebpackPlugin(['dist/*'], { root: path.resolve(__dirname, '../') }), ]
webpack4 中使用 mini-css-extract-plugin 插件来分离 css。
// 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, } }, }, ] }, ]
同时也须要安装 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/ }, ]
// 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
安装 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 的区别