Webpack 作为前端最火的构建工具,是前端自动化工具链最重要的部分。本文主要分为两部分,第一部分:比较 Webpack3 版本和 Webpack4 版本区别;第二部分:Webpack4版本基础配置。该文是笔者自己的学习记录和知识总结,比较基础。也希望文章能够对Webpack使用不太熟悉的同学有所帮助。
Part1: Webpack3 与 Webpack4 版本区别
environment
首先在环境上,Webpack4 版本不再支持node.js4了,这也就意味着如果项目中使用了nodejs4版本的话,并且想要升级至 Webpack4, 需要先升级当前项目node版本。
mode构建模式
Webpack4版本增加mode配置,提供了三种构建模式可供选择 development,production和none,默认为 production, 不同模式下默认的配置也有所不同。
- development默认配置:
1 2 3 4 5 6 7 8 9
| devtool: eval //调试 cache:true //缓存模块, 避免在未更改时重建它们。 module.unsafeCache:true //缓存已解决的依赖项, 避免重新解析它们。 output.pathinfo:true //在 bundle 中引入「所包含模块信息」的相关注释 optimization.providedExports:true //在可能的情况下确定每个模块的导出,被用于其他优化或代码生成。 optimization.splitChunks:true //找到chunk中共享的模块,取出来生成单独的chunk optimization.runtimeChunk:true //为 webpack 运行时代码创建单独的chunk optimization.namedModules:true //给模块有意义的名称代替ids optimization.namedChunks:true //给chunk有意义的名称代替ids
|
- production默认配置:
1 2 3 4 5 6 7
| optimization.flagIncludedChunks:true //某些chunk的子chunk已一种方式被确定和标记,这些子chunks在加载更大的块时不必加载 optimization.occurrenceOrder:true //给经常使用的ids更短的值 optimization.usedExports:true //确定每个模块下被使用的导出 optimization.sideEffects:true //识别package.json or rules sideEffects 标志 optimization.noEmitOnErrors:true //编译错误时不写入到输出 optimization.concatenateModules:true //尝试查找模块图中可以安全连接到单个模块中的段。- - optimization.minimize:true //使用uglify-js压缩代码
|
当mode为none时,之前 webpack 的默认配置会全部失效。(最好不要设置mode为none)
optimization 配置项
前面我们看到不同mode模式下,webpack 会有不同的默认配置项,这样我们就可以减少之前版本一些插件的使用了。
NamedModulesPlugin插件 –> optimization.namedModules(默认只在development模式)
ModuleConcatenationPlugin 插件 –> optimization.concatenateModules(默认只在 production 模式)
NoEmitOnErrorsPlugin 插件 –> optimizat on.noEmitOnErrors(默认只在 production 模式)
UglifyJsPlugin –> optimization.minimize(默认只在 production 模式)
CommonsChunkPlugin 插件–> optimization.splitChunks和optimization.runtimeChunk
具体修改配置可见:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); optimization: { runtimeChunk: { name: 'manifest' }, splitChunks: { chunks: "all", cacheGroups: { commons: { name: "commons", test: /src[\/]/, chunks: "initial", priority: 2, minChunks: 2 }, vendors: { name: "vendors", test: /node_modules/, chunks: "initial", priority: 10, minChunks: 2 } } }, minimize: true, noEmitOnErrors: true, minimizer: [ new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }), new OptimizeCSSAssetsPlugin({}) ]
|
并且 webpack 在运行的时候会根据mode设置一个process.env.NODE_ENV全局变量,可以实现不同的环境执行不同的代码,这样也就减少了DefinePlugin的使用。
Plugins
在 webpack4 版本中推荐使用 mini-css-extract-plugin 插件来替代 extract-text-webpack-plugin 抽取css到单独文件中,使用 optimize-css-assets-webpack-plugin 对 css 进行压缩处理
无需定义入口点和输出文件
Webpack 会查找./src/index.js作为默认入口点。 并且也无需定义输出文件,缺省为./dist/main.js。(这种特性的重要意义在于处理小型项目的时候,不需要配置文件,只需要webpack)。
支持多种模块类型
Webpack4 版本现在支持以下五种模块类型:
JavaScript/auto: Webpack3.0 在多种模块系统中支持了这种模块类型,比如 CommonJS, AMD, ESM
JavaScript/esm: 只支持 ECMAScript 模块
JavaScript/dynamic: 只支持 CommonJS 和AMD.
Json: 支持JSON数据,可以通过require和import使用
Webassembly/experimental: 只支持 WebAssembly
Part2: Webpack4 版本基础配置
先看下面这份 Webpack 的配置文件,如果面前的你已经能够完全读懂每一项,那么你可以快速浏览或者直接略过以下内容,如果你对其中还有一些疑惑,可以花一些时间慢慢阅读下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| const path = require('path'); const webpack = require('webpack'); // 需要引入webpack模块
const MiniCssExtractPlugin = require("mini-css-extract-plugin") const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件 const ExtractTextPlugin = require('extract-text-webpack-plugin') //引入分离插件 module.exports = { devtool: "eval-source-map", entry: { index: path.join(__dirname,'src/index.js'), child: path.join(__dirname,'src/child.js') }, output: { path: path.join(__dirname, './dist'), filename: '[name].js' }, // 提取公共代码 /* optimization: { splitChunks: { cacheGroups: { vendor: { // 抽离第三方插件 test: /node_modules/, // 指定是node_modules下的第三方包 chunks: 'initial', name: 'vendor', // 打包后的文件名,任意命名 // 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包 priority: 10 }, common: { // 抽离自己写的公共代码,utils这个名字可以随意起 chunks: 'initial', name: 'common', // 任意命名 minSize: 0 // 只要超出0字节就生成一个新包 } } } }, */ module: { rules: [ { test: /(\.jsx|\.js)$/, //匹配jsx和js结尾的文件 use: { loader: 'babel-loader', // 当loader传入的参数过多时,可以添加options对象 options: { cacheDirectory: true //cacheDirectory用于缓存babel的编译结果,加快重新编译的速度 } }, include: path.join(__dirname,'src') //字面意思,src下面的所有jsx和js文件都会被babel-loader解析 }, { test: /.css$/, // use: ['style-loader','css-loader'] //这里注意调用loader时是从右到左编译的。 use: ExtractTextPlugin.extract({ // 调用分离插件内的extract方法 fallback: 'style-loader', use: ['css-loader'] }) }, { test: /.(scss|sass)$/, // use:['style-loader','css-loader', 'sass-loader'] use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader', 'sass-loader'] }) }, { test: /.less$/, // use:['style-loader','css-loader', 'less-loader'] use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader', 'less-loader','postcss-loader'] }) }, { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192, outputPath: 'images' // 设置打包后图片存放的文件夹名称 } }] }, { test: /\.tsx?$/, use: { loader: 'ts-loader' } } ] }, plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname,'./src/index.tmpl.html') //模板,webpack默认会生成index.html文件 }), new webpack.HotModuleReplacementPlugin(), // 热更新插件 new ExtractTextPlugin('css/index.css') // 将css统一分离到/dist文件夹下的css文件夹中的index.css ], devServer: { contentBase: path.join(__dirname, './dist'), //服务器读取文件目录 port: 9001, //运行的端口号 inline: true, // 文件修改后实时刷新 historyApiFallback: true, //设为true,所有的页面都将跳转到index.html hot: true, // 热更新 proxy: { // "/api": "http://localhost:9000/api/" } // 代理到后端服务接口,当我们需要后端联调的时候可以配置 } }
|
1.基础构建
创建并进入文件夹,npm init 初始化项目,安装 webpack 和 webpack-cli
1 2 3
| mkdir react-webpack && cd react-webpack && npm init npm install -g webpack npm install webpack webpack-cli --save-dev
|
按以下目录结构创建文件。
然后编写index.html和index.js基础代码,并在 webpack.config.js 配置文件中配置入口和输出文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| const path = require('path'); module.exports = { entry: path.join(__dirname,'src/index.js'), output: { path: path.join(__dirname, './dist'), filename: 'bundle.js' } }
修改package.json "script": { "start": "webpack --config webpack.config.js" }
|
终端运行npm start,浏览器查看运行结果。
这样我们的 webpack 就可以正常使用了。
2. entry
entry顾名思义它是 webpack 通往天堂/地狱的入口。我们也从这里开始 webpack 的旅途。
entry有以下几种配置:
1 2 3 4 5 6 7 8 9 10 11 12
| // 单文件入口 entry: path.join(__dirname,'src/index.js'),
// 多文件入口 数组写法:webpack会将数组里面的文件一起打包到bundle.js entry: ['./src/index.js','./src/test.js']
对象写法:打包之后会生成不同的文件存放 entry: { index: './src/index.js', test: './src/test.js' }
|
3. output
output 可以控制 webpack 如何向硬盘写入编译文件。
- filename: 输出的文件名,与entry相匹配,js/[name].js 中name指的就是entry中的index, test
- path: 目标输出目录路径
- publicPath: 文件输出公共路径
1 2 3 4
| output: { path: path.join(__dirname, './dist'), filename: '[name].js' }
|
注意:即使我们有多个entry 入口起点,但只指定一个输出配置。
4. mode
mode配置为 webpack4 新增,只需在配置文件中配置或者cli参数中传递。
1 2 3 4 5 6
| // 方法一 module.exports = { mode: 'production' } // 方法二 webpack --mode=production
|
更多mode详细介绍可以参考Part1
5. 构建本地服务器(webpack-dev-server)
从上图运行结果的路径我们可以看到页面是从本地文件打开的,在项目开发中为了提高开发效率,我们需要搭建本地服务器。
安装 webpack-dev-server
1
| npm install webpack-dev-server --save-dev
|
简单配置下devServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| devServer: { contentBase: path.join(__dirname, './dist'), //服务器读取文件目录 port: 9001, //运行的端口号 inline: true, // 文件修改后实时刷新 historyApiFallback: true, //设为true,所有的页面都将跳转到index.html proxy: { // "/api": "http://localhost:9000/api/" } // 代理到后端服务接口,当我们需要后端联调的时候可以配置 } 修改package.json "script": { "start": "webpack --config webpack.config.js", "dev": "webpack-dev-server --config webpack.config.js --color --progress --hot --open" }
|
从浏览器中可以看到我们项目已经跑在本地了。
6. 支持 es6, react.js, TypeScript.js 等
babel是一个编译 JavaScript 的平台,它可以把我们编写的ES6,ES7代码以及react的JSX等编译成浏览器可识别的js文件。
安装babel依赖包, react, typescript
1 2 3
| npm install babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 --save-dev npm install react react-dom typescript ts-loader --save npm install typescript ts-loader --save-dev
|
新建.babelrc 配置文件
1 2 3 4 5 6 7 8 9
| { "presets": [ "es2015", "react", "stage-0" ], "plugins": [ ] }
|
webpack.config.js 使用babel-loader解析js文件, ts-loader 解析ts语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| module: { rules: [ { test: /(\.jsx|\.js)$/, //匹配jsx和js结尾的文件 use: { loader: 'babel-loader', // 当loader传入的参数过多时,可以添加options对象 options: { cacheDirectory: true //cacheDirectory用于缓存babel的编译结果,加快重新编译的速度 } }, include: path.join(__dirname,'src') //字面意思,src下面的所有jsx和js文件都会被babel-loader解析 }, { test: /\.tsx?$/, use: { loader: 'ts-loader' } } ] },
|
命令行输入:tsc–init
生成 tsconfig.json 文件,修改配置
1 2 3 4 5 6 7 8 9
| { "compilerOptions": { "sourceMap": true, "noImplicitAny": true, "module": "commonjs", // 包含es6 es7 和commonjs "target": "es5", // 目标运行环境 "jsx": "react" } }
|
修改index.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { Component } from 'react'; import ReactDOM from 'react-dom'; class Hello extends Component { render() { return ( <div> hello, webpack,i'm react </div> ) } } ReactDOM.render( <Hello/>, document.getElementById('root') )
|
运行报错
原来是 babel-core 和 babel-loader 的版本冲突了,我们安装7版本的 babel-loader 就可以了。
1 2 3
| npm install babel-loader@7.1.4 --save-dev
// 终端输入 npm run dev
|
恭喜 react 和 typescript 环境配置成功。
7. 编译css文件(less,sass)
1 2 3
| npm install css-loader style-loader --save-dev // css 文件 npm install sass-loader node-sass --save-dev // sass文件 npm install less less-loader --save-dev // less文件
|
webpack.config.js 添加配置
1 2 3 4 5 6 7 8 9 10 11 12
| { test: /.css$/, use: ['style-loader','css-loader'] //这里注意调用loader时是从右到左编译的。 }, { test: /.(scss|sass)$/, use:['style-loader','css-loader', 'sass-loader'] }, { test: /.less$/, use:['style-loader','css-loader', 'less-loader'] }
|
注意:调用loader时是从右到左编译的。
8. 支持图片
1
| npm install --save-dev url-loader file-loader
|
- file-loader 解决css等文件中引入图片路径的问题
- url-loader 当图片较小的时候会把图片BASE64编码,大于limit参数的时候还是使用file-loader 进行拷贝
webpack.config.js 添加配置
1 2 3 4 5 6 7 8 9
| { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }
|
控制是否生成,以及如何生成 source map文件,开发环境下更有利于定位问题,默认 false,
开启之后会影响到编译速度,生产环境记得关闭这个功能。
devtool的值有许多,常见 cheap-eval-source-map、eval-source-map、cheap-module-eval-source-map、inline-cheap-module-source-map等等,
这里主要介绍几种:
1 2 3 4
| source-map 把映射文件生成到单独的文件,最完整最慢 cheap-module-source-map 在一个单独的文件中产生一个不带列映射的Map eval-source-map 使用eval打包源文件模块,在同一个文件中生成完整sourcemap cheap-module-eval-source-map sourcemap和打包后的JS同行显示,没有映射列
|
更详细可见官网Concepts
webpack 配置文件中增加
1
| devtool: "eval-source-map",
|
10. 从js中分离css
安装 mini-css-extract-plugin插件
1
| npm install --save-dev mini-css-extract-plugin
|
改写 webpack 配置文件
1 2 3 4 5 6 7 8 9 10
| const MiniCssExtractPlugin = require("mini-css-extract-plugin")
{ test: /\.(scss|sass)$/, use: [ MiniCssExtractPlugin.loader, "css-loader", "sass-loader" ] }
|
11. 增加css前缀
1
| npm install postcss-loader autoprefixer --save-dev
|
新建 postcss.config.js
1 2 3 4 5 6 7
| module.exports = { plugins: [ require('autoprefixer') // 引用autoprefixer模块 ] }
// 在css-loader中再引入postcss-loader即可
|
12. 简化HTML文件创建
1
| npm install html-webpack-plugin --save-dev
|
webpack 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const path = require('path'); const webpack = require('webpack'); // 需要引入webpack模块 const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件 module.exports = { entry: path.join(__dirname,'src/index.js'), output: { path: path.join(__dirname, './dist'), filename: 'bundle.js' }, module: { }, plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname,'./src/index.tmpl.html') }) ], devServer: { } }
// package.json文件里添加 "build": "webpack --mode production --config webpack.config.js"
|
之后我们npm run build会发现自动生成dist文件夹,这是因为我们在output出口配置了dist/bundls.js,这样HtmlWebpackPlugin会自动帮我们在index.html中引用bundle.js,不需要手动引用了。
13. 启用热替换模块 (HMR)
1 2
| npm install --save-dev react-hot-loader 因为这个插件是依赖于webpack-dev-server的,所有要在devServer配置项中开启热更新 hot: true
|
webpack 配置文件
1 2 3 4 5 6
| plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname,'./src/index.tmpl.html') }) new webpack.HotModuleReplacementPlugin(), // 热更新插件 ]
|
注意:不要在生产环境(production)下启用 HMR
14. optimization(webpack4新增)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 提取公共代码 optimization: { splitChunks: { cacheGroups: { vendor: { // 抽离第三方插件 test: /node_modules/, // 指定是node_modules下的第三方包 chunks: 'initial', name: 'vendor', // 打包后的文件名,任意命名 // 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包 priority: 10 }, common: { // 抽离自己写的公共代码,common这个名字可以随意起 chunks: 'initial', name: 'common', // 任意命名 minSize: 0 // 只要超出0字节就生成一个新包 } } } },
|
这一部分Part1有详细介绍。
还有很多 plugins 以及优化的知识本文没有介绍到,更多 webpack 知识可去官网了解。
就到这里吧
关于 Webpack4 的一些常规配置就说到这里了,其实在工作当中更多时候,可能并不需要我们去配置这些内容,但是掌握这些技能是必要的。本文对 webpack 的讲述仍有许多不完善的地方,更多知识我们一起学习。文章如有错误的地方,也欢迎指教。