🧲五、使用plugins让打包更便携
1、html-webpack-plugin
在学习了如何使用 loader
来打包静态文件之后,接下来我们一起来了解在 webpack
中,如何使用 plugins
让打包更便捷。
我们在打包项目时, webpack
总是会把打包后的内容放到 dist
目录下。这个时候我们可能还需要自行再去创建一个 index.html
来引入核心文件。那这样会不会就显得略有点麻烦了?
因此, webpack
给我们提供了 plugin
插件来解决这个问题。
首先,我们先来安装 plugin
,具体命令行如下:
npm install html-webpack-plugin -D 复制代码
接下来我们将插件引入 webpack.config.js
当中,具体代码如下:
const HtmlWebpackPlugin = require('html-webpack-plugin'); plugins: [new HtmlWebpackPlugin({ //表明要引用哪一个模板 template: 'src/index.html' })] 复制代码
现在,我们来梳理一下 htmlWebpackPlugin
如何帮我们完成自动打包。
首先, htmlWebpackPlugin
会在打包结束后,自动生成一个 html
文件。之后呢,把打包生成的 js
文件自动引入到这个 html
文件中。
所以,从某种程度上来说就是, plugin
可以在 webpack
运行到某个时刻的时候,自动地帮你做一些事情。即当我们打包结束的这儿一时刻, plugin
会自动帮我们创建 html
文件以供我们直接使用。
2、clean-webpack-plugin
有时候我们有可能对 webpack.config.js
中的output所对应的filename进行修改,这就很容易导致在打包过程中遇到多文件冲突。
那么我们想要实现的就是,在打包时,先清空原来的dist文件夹,然后再生成一个新的dist文件夹。如何处理呢?请看下方。
首先我们先安装依赖 clean-webpack-plugin
,具体命令行如下:
npm install clean-webpack-plugin -D 复制代码
接下来我们将插件引入 webpack.config.js
当中,具体代码如下:
const CleanWebpackPlugin = require('clean-webpack-plugin'); plugins: [new HtmlWebpackPlugin({ //表明要引用哪一个模板 template: 'src/index.html' }),new CleanWebpackPlugin(['dist'])] 复制代码
通过以上代码,就可以在我们项目打包时,先删除 dist
文件夹,之后再创建一个新的文件夹。
🗞️六、Entry和Output
接下来我们再来看 webpack
中的 entry
和 output
中几个比较核心的配置。
module.exports = { mode:'development', // 放置入口文件,明确怎么打包 entry:{ main: './src/index.js', sub: './src/index.js' }, plugins: [new HtmlWebpackPlugin({ //表明要引用哪一个模板 template: 'src/index.html' }),new CleanWebpackPlugin(['dist'])], // 输出,表明webpack应该怎么输出 output: { //如果把资源放在cdn下,则引入cdn publicPath: 'http://cdn.com.cn', //当entry有多个入口文件时,用[]可以输出多个文件 filename: '[name].js', // 指打包后的文件要放在哪个文件下 path: path.resolve(__dirname, 'dist') } } 复制代码
🗺️七、SourceMap
1、引例阐述
有时候,我们在写代码时,总会莫名的出bug。看着控制台那红红的报错,心里总归很不是滋味。同时,如果我们没有配置好 webpack
的话,那错误找起来简直是很恐怖的。
比如,在开发模式下,我们默认 webpack.config.js
像下面这样配置,具体代码如下:
module.exports = { mode:'development', devtool: 'none', entry:{ //打包到dist目录下的main.js main: './src/index.js' }, output: { //用[]可以生成多个文件 filename: '[name].js', // 指打包后的文件要放在哪个文件下 path: path.resolve(__dirname, 'dist') } } 复制代码
然后呢,假设我们现在代码里面错把 console.log
写成 consele.log
。那么现在控制台的打印效果如下:
大家可以看到,此时的错误定位到打包后的 main.js
文件里面的第96行。那试想一下,如果我们的业务代码特别多,报错有可能就是在文件中的上前行了。
这样的场景并不是我们想看到的。我们想做的事情呢就是,希望 webpack
打包完成之后就把错误直接抛给我们,并把其对应的具体文件地址显示出来。也就是我们出错的那个代码文件,而不是打包后的文件 main.js
。
因此, webpack
给我们提供了 sourceMap
这个配置,来解决这个问题。
2、sourceMap
我们现在把 devtool
这个配置,改成 sourceMap
。具体代码如下:
module.exports = { mode:'development', devtool: 'source-map', entry:{ //打包到dist目录下的main.js main: './src/index.js' }, output: { //用[]可以生成多个文件 filename: '[name].js', // 指打包后的文件要放在哪个文件下 path: path.resolve(__dirname, 'dist') } } 复制代码
改完之后呢,我们来看一下控制台的打印结果:
现在大家可以看到,改成 source-map
的配置之后,报错的定位直接到了我们自己所编写代码的目录下,即 index.js
。而不再是大海捞针似的在 main.js
里面找。
3、sourceMap常见配置
看完上面的例子之后,相信大家对 SourceMap
有了一定的了解。接下来我们来看一下 sourceMap
的一些常见配置。具体看看下方:
SourceMap | 含义 |
inline-source-map | 报错时将行和列都显示出来 |
cheap-inline-source-map | 报错时只知道哪一行出错了,不知道在哪一列 |
cheap-module-source-map | 生产环境最佳实践,不仅管自己的业务代码错误,还要管其他的其他的错误,像loader、其他第三方模块的错误等等 |
eval | eval是打包速度最快的一种方式,但如果遇到业务代码比较复杂的情况下,用eval提示出来的效果可能不太全面 |
module-eval-source-map | 用module,表明不仅要显示业务错误,还要显示loader、第三方错误等等 |
cheap-module-eval-source-map | 开发环境最佳实践 |
🧱八、使用WebpackDevServer提升开发效率
1、--watch
事实上,如果我们不采用 WebpackDevServer
的方式来开发的话,那么我们每一次想要查看编译后的运行结果,都需要先命令行编译 npm run bundle
命令,之后再打开 dist
目录下的 index.html
文件才能重新查看。这样一来二往的,难免效率低下。我们期待的结果是什么呢?
我们把 package.json
文件里的 script
进行一番改造,具体代码如下:
"scripts": { "watch": "webpack --watch", "bundle": "webpack" }, 复制代码
通过以上代码大家可以看到,将 webpack
后面加上 --watch
字段,然后运行 npm run watch
,就可以每次修改完代码后, webpack
实现自动监听,而不用像以往那样,每修改一次代码都要对再重新运行命令来对 webpack
进行打包。
2、webpackDevServer
但是呢,这种方式可能还不够友好,毕竟开发者总是懒惰的,能尽量让程序来干活就不要用手工来干活。
实际上我们想要达到的效果是,当我们运行完 npm run watch
这行命令的时候,不仅能自动帮我们实现打包,同时还能帮我们打开控制台,并且模拟一些服务器上的特性。那么我们就可以通过 webpackDevServer
来实现我们想要的效果。如何使用 webpckDevServer
呢?具体看下方。
(1)安装webpackDevServer
我们现在项目中安装webpackDevServer,具体命令行如下:
npm install webpack-dev-server -D 复制代码
(2)配置package.json文件
接下来我们来配置 package.json
文件的 script
。具体代码如下:
"scripts": { "watch": "webpack --watch", "start": "webpack-dev-server" }, 复制代码
(3)配置 webpack文件
接下面我们来配置 webpack.config.js
文件,具体代码如下:
module.exports = { mode:'development', devtool: 'source-map', // 放置入口文件,明确怎么打包 entry:{ main: './src/index.js' }, devServer: { contentBase: './dist', // 当运行完npm run start时,会自动的帮我们打开浏览器 open: true }, output: { //用[]可以生成多个文件 filename: '[name].js', // 指打包后的文件要放在哪个文件下 path: path.resolve(__dirname, 'dist') } } 复制代码
那么现在,我们来看下, webpackDevServer
如何做到自动打开浏览器。详情见下图:
大家可以看到,通过 webpackDevServer
,它不但会监听到我们的文件发生了改变,重新帮我们进行打包。同时它还会自动的帮我们重新刷新浏览器,并且会自动地帮我们打开浏览器。所以用它呢,可以大大提升我们的代码开发效率。
(4)配置端口号
webpackDevServer
默认我们服务器的端口号是 8080
,如果我们想要修改它为其他的端口号,该怎么做呢?
我们需要在来修改 webpack.config.js
文件下的 DevServer
,具体代码如下:
module.exports = { mode:'development', devtool: 'source-map', // 放置入口文件,明确怎么打包 entry:{ main: './src/index.js' }, devServer: { contentBase: './dist', // 当运行完npm run start时,会自动的帮我们打开浏览器 open: true, //修改端口号 port: 3000 }, output: { //用[]可以生成多个文件 filename: '[name].js', // 指打包后的文件要放在哪个文件下 path: path.resolve(__dirname, 'dist') } } 复制代码
我们只需要在 devServer
里面加上一个 port
的配置,即可实现自定义端口号。
同时值得注意的是,当我们在用 webpackDevServer
帮我们项目做打包时,它不会自动生成 dist
目录,那这是为什么呢?用 webpackDevServer
打包后的项目会放在我们的电脑内存中,这在某种程度下可以有效的提升项目的打包速度,让打包变得更快。
🌡️九、Hot Module Replacement 热模块更新
1、引例阐述
假设我们现在要实现一个新增元素的功能,这个功能所要达到的效果是每点击一次按钮,就新添加一次文本 item
。具体实现代码如下:
index.js文件:
import './style.css'; var btn = document.createElement('button'); btn.innerHTML = '新增'; document.body.appendChild(btn); btn.onclick = function(){ var div = document.createElement('div'); div.innerHTML = 'item'; document.body.appendChild(div); } 复制代码
style.css文件:
div:nth-of-type(odd){ background: yellow; } 复制代码
此时浏览器的显示效果如下图所示:
假设我们现在来给css的背景改个颜色,比如说改成紫色。具体代码如下:
div:nth-of-type(odd){ background: purple; } 复制代码
此时我们保存后浏览器会重新进行刷新,之后每一个 item
又要重新 append
进来。如下图:
那这种情况下可能就不是我们想要的结果了。我们希望的是,所有的 item
不进行重新刷新,并且当 css
样式改变的时候,对应的 item
颜色也可以得到改变。那么这就要引出 webpackDevServer
中的一个内容:热模块更新 Hot Module Replacement
。接下来我们来了解热模块更新相关的配置。
2、热模块更新配置
热模块更新,即Hot Module Replacement,简称为 HMR
。
接下来我们在 webpack.config.js
文件夹下进行配置。具体代码如下:
//node的核心模块 const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); module.exports = { mode:'development', devtool: 'source-map', // 放置入口文件,明确怎么打包 entry:{ main: './src/index.js' }, devServer: { contentBase: './dist', // 当运行完npm run start时,会自动的帮我们打开浏览器 open: true, port: 8080, // 让我们的webpackDevServer开启hotModuleReplacement这样子的功能 hot: true, // 即便HMR没有生效,也不让浏览器自动刷新 hotOnly: true }, module:{ rules:[{ test:/\.(jpg|png|gif)$/, use:{ loader:'file-loader', options: { //placeholder 占位符 name: '[name]_[hash].[ext]', outputPath: 'images/', limit: 10240 } } },{ test:/\.scss$/, use:[ 'style-loader', { loader: 'css-loader', options: { //表明前面要先走sass-loader和postcss-loader importLoaders: 2, modules: true } }, 'sass-loader', 'postcss-loader' ] },{ test:/\.css$/, use:[ 'style-loader', 'css-loader', 'postcss-loader' ] }] }, plugins: [new HtmlWebpackPlugin({ //表明要引用哪一个模板 template: 'src/index.html' }),new CleanWebpackPlugin(['dist']), new webpack.HotModuleReplacementPlugin() ], // 输出,表明webpack应该怎么输出 output: { // 下载middle:npm install express webpack-dev-middleware -D publicPath: '/', //用[]可以生成多个文件 filename: '[name].js', // 指打包后的文件要放在哪个文件下 path: path.resolve(__dirname, 'dist') } } 复制代码
通过以上代码我们可以知道,配置 devServer
下的 hot
和 hotOnly
,以及 plugins
下的 new webpack.HotModuleReplacementPlugin()
,来达到热模块更新的效果。
接下来我们来看一下,进行配置之后,浏览器的效果。详情见下图:
大家可以看到,加上这几个配置之后, item
不会再重新刷新了,而是在原来的基础上进行样式修改。
📀十、使用Babel处理ES6语法
1、ES6语法转换为ES5语法
继续,接下来我们来了解,如何使用webpack和babel,来编写ES6的语法。
大家都知道,ES6的语法规范是2015年才正式出版的。所以有时候,并不是所有的浏览器都支持ES6语法。因此,我们现在想要做的事情就是,在webpack打包时,能够将ES6的语法转换为ES5的语法。这样,项目运行的时候,浏览器就不会报错了。
那怎么实现这样子的打包呢?接下来我们一起来了解一下。
首先我们打开babel的官方网站,按照步骤,我们一步步在 webpack
中使用 babel
。
第一步: 安装 babel-loader
和 @babel/core
这两个库。具体代码如下:
npm install --save-dev babel-loader @babel/core 复制代码
babel-loader
是帮助webpack进行打包使用的一个工具,而 @babel/core
则是 babel
的一个核心库,它能够让 babel
去识别 js
代码里的内容,然后呢把 js
代码转换成 AST
抽象语法树,之后再把抽象语法树编译成一些新的语法。
第二步: 在 webpack.config.js
文件下的配置项里增设规则。具体代码如下:
module: { rules: [ { test: /\.m?js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ['@babel/preset-env'] } } } ] } 复制代码
第三步: 安装 @babel/preset-env
。具体代码如下:
npm install @babel/preset-env --save-dev 复制代码
为什么要安装这个模块呢?实际上,当我们使用 babel-loader
处理文件时,实际上 babel-loader
只是 webpack
和 babel
之间做通信的一个桥梁,它只是帮我们打开了一个通道,但是它并不会帮我们把 ES6
的语法转换为 ES5
的语法。所以,我们就还需要借助一些其他的模块,来做这项工作。这个模块就是前面我们说的,preset-env 。
babel/preset-env
,包含了所有 ES6
转换为 ES5
的语法规则,当使用此模块打包时,就可以把我们所有 js
中 ES6
的代码转换为 ES5
了。具体配置方式见以上第二步。
2、Babel-polyfill
通过以上的方式,我们可以达到将ES6语法转换为ES5语法的效果。但是呢,我们还要考虑到的一个问题就是,如果遇到像promise这一类新的语法变量,或者时像数组里面map这一类的函数,低版本的浏览器里面,实际上还是不存在的。虽然我们做了语法解释和语法翻译,但也只是翻译了一部分。还有一些对象和函数,在低版本的浏览器还是没有的。
所以呢,这个时候我们不仅要使用 babel/preset-env
做语法转换,还要把这些缺失的变量和函数补充到低版本的浏览器里面。
那怎么补充呢,这个时候我们就需要借助 babel-polyfill
这个工具来进行补充。 babel-polyfill
旨在用于实现浏览器并不支持的原生API的代码
。接下来讲解这个模块的使用操作。
第一步: 定位到官方文档,安装 babel-polyfill
。具体代码如下:
npm install --save @babel/polyfill 复制代码
第二步: 引入该模块。具体代码如下:
import "@babel/polyfill"; 复制代码
通常情况下,这段代码放到项目的 js
入口文件下。
第三步: 改造 webpack.cofig.js
文件下的 module
,减少打包大小。具体代码如下:
module: { rules: [ { test: /\.m?js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: [['@babel/preset-env'],{ useBuiltIns: 'usage' }] } } } ] } 复制代码
这段代码的意思就是,当用 babel-polyfill
填充低版本浏览器特性的时候,不是把是多有的特性都加进来,而是根据我们的业务代码来决定到底到加什么。
同时, babel-preset
也有很多其他值得学习的配置属性,这里不再进行讲解。大家可以自行到官方文档上进行查看~
📚十一、结束语
写完这篇文章的时候,突然想起上次面试时的面试官。在最后的反问环节问他关于 webpack
的问题,他说 webpack
一般会让对公司业务很熟悉的员工来处理,毕竟前端工程化不是儿戏。
当时我还没有很大的感触,但现在学到这里突然就想到了那个场景。确实是这样,我这才学了不到它的冰山一角,就已经感到 webpack
的庞大工程了。如果在打包时候,但凡有一个小地方的配置出现问题,就有可能引发整个项目的不可收拾局面。(当然一般情况下不会出现这样的情况,言重了……)
在学习 webpack
的过程中,要明确好自己所使用的webpack版本。比如周一刚开始迷迷糊糊的,感觉版本4和版本5都差不多。但对于 webpack
来说,这完全就是在跟它开玩笑。每一个依赖都有4和5对应的版本,而不是说想用哪个就哪个。如果胡乱使用的话,无形之中可能会报错到怀疑人生……
因此,确定好此时用 webpack
打包时所使用的版本,并在使用 npm
依赖时也同样要找到对应的版本来进行使用,降低错误的发生。
到这里,关于webpack的超入门知识就讲到这里啦!希望对大家有帮助~