1. mode

在 webpack.config.js 文件中配置模式

  1. // development--开发环境
  2. // production--生产环境
  3. mode: 'development'

在项目开发的时候使用开发环境,上线的时候使用生产环境

2. source map

当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直接指向到 bundle.js。你有时候很难知道错误来自于哪个源文件

为了更容易地追踪 error 和 warning,webpack 内置了 source maps 功能,可以将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你

webpack.config.js文件中配置 devtool 属性:

  1. // 能够准确的捕获代码方式错误的位置
  2. // 开发中推荐使用 'cheap-module-source-map',生产环境一般不开启 sourcemap
  3. devtool: 'cheap-module-source-map'

webpack-devtool 共提供了7种 SourceMap 模式:
image.png
项目入口文件 index.js :

  1. class Hello {
  2. sayHello() {
  3. console.log('Hello Webpack5!!!')
  4. }
  5. }
  6. var a = new Hello()
  7. a.sayHello()

① eval:
这是devtool默认值,即使我们不设置devServer,项目也默认执行devtool:'eval'
每个 module 通过eval来执行

复制打包后 index.html 的路径进入浏览器:
evalgif.gif
发现可以追踪到源码的输出,再观察打包后的 bundle,发现它会返回一个 sourceURL
image.png
② source-map
会打包出一个单独的 sourceMap 文件,而且 bundle 最后会追加一个 sourceMappingURL,它的值为 sourceMap 文件名(两者作关联)
image.png
image.png
第四章  搭建开发环境 - 图6
③ hidden-source-map
依然会打包出一个 sourceMap 文件,但是 bundle 最后不会有 sourceMappingURL,即生成的 sourceMap 文件不会和 bundle 作关联了。最重要的是,它不能在锁定源代码的行数了
第四章  搭建开发环境 - 图7
image.png
hidden-source-map.gif
④ inline-source-map
不会生成单独的 sourceMap 文件,bundle 末尾追加一个 sourceMappingURL,它的值是一个 dataURL,而且可以锁定源代码的行数。它和source-map的区别就是,它不会把 sourceMap 给打包出来
image.png
第四章  搭建开发环境 - 图11
⑤ eval-source-map
不会生成单独的 sourceMap 文件,bundle 末尾追加一个 sourceMappingURL,它的值是一个 dataURL,而且可以锁定源代码的行数
每个 module 通过eval来执行,并且会生成 dataURL 形式的 sourceMap
image.png
image.png
第四章  搭建开发环境 - 图14
⑥ cheap-source-map
会打包出单独的 sourceMap 文件,bundle 末尾会追加一个 sourceMappingURL,它的值为 sourceMap 文件名(两者作关联)

cheap-source-map文件中只记录源代码的行(减少文件的体积),而且也不包含loader信息

注:它可以锁定源代码的行数;但是它不能锁定通过 babel-loader 解析的代码行数
image.png
第四章  搭建开发环境 - 图16
⑦ cheap-module-source-map(推荐)
会打包出单独的 sourceMap 文件,bundle 末尾会追加一个 sourceMappingURL,它的值为 sourceMap 文件名(两者作关联)

注:它可以锁定源代码的行数,而且它也能锁定通过 babel-loader 解析的代码行数
第四章  搭建开发环境 - 图17
第四章  搭建开发环境 - 图18

source-mapcheap-source-mapcheap-module-source-map的区别:

  • 相同点:会打包出单独的 sourceMap 文件,bundle 末尾会追加一个 sourceMappingURL,它的值为 sourceMap 文件名(两者作关联)
  • 不同点:
  • source-map文件中会记录源代码代码的行和列(文件体积较大);
  • cheap-source-map文件中只记录源代码的行(减少文件的体积),而且也不包含loader信息;
  • cheap-module-source-map文件中只记录源代码的行(减少文件的体积),而且包含loader信息;

要注意的是,生产环境我们一般不会开启sourcemap功能,主要有两点原因:
1. 通过 bundle 和 sourcemap 文件,可以反编译出源码————也就是说,线上产物有 soucemap 文件的话,就意味着有暴漏源码的风险
2. 我们可以观察到,sourcemap 文件的体积相对比较巨大,这跟我们生产环境的追求不同,生产环境追求更小更轻量的 bundle

3. watch mode

在每次编译代码时,手动运行 npx webpack 会显得很麻烦。
我们可以在 webpack 启动时添加 “watch” 参数。如果其中一个文件被更新,代码将被自动重新编译,不必再去手动运行整个构建

npx webpack —watch

唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器。如果浏览器也能够自动刷新就好了,因此我们会通过 webpack-dev-server 实现此功能

4. devServer

开发环境下,我们往往需要启动一个 web 服务,方便我们模拟一个用户从浏览器中访问我们的 web 服务,读取我们的打包产物,以观测我们的代码在客户端的表现。 webpack 内置了这样的功能:
webpack-dev-server ,它提供了一个基本的 web 服务,具有实时重新加载功能

  1. 安装依赖:

    npm i webpack-dev-server

  2. 配置 devServer ( webpack.config.js ) ```javascript const path = require(‘path’)

module.exports = { devServer: { static: path.resolve(__dirname, ‘./dist’), compress: true, port: 8080, headers: { ‘x-token’: ‘abc123’ } } }

  1. `static`配置好后,输入命令 `npx webpack-dev-server --open`,当项目代码改动时,会自动进行打包并且自动进入浏览器进行查看,执行这个命令它会开启服务器,提供网络路径供你访问<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2158318/1642570102662-eeed78ec-7a2a-4a5d-a51b-1ab4949fc00e.png#clientId=uc7d117b1-8315-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=87&id=RwXo9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=87&originWidth=591&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14933&status=done&style=none&taskId=udb52c5a4-e8c9-4f2d-9d94-79dbfc17e5c&title=&width=591)<br />浏览器也会提示:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2158318/1642570190282-7de6e14c-e7f9-4a58-8a33-74c2aa0c6457.png#clientId=uc7d117b1-8315-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=41&id=RmoZZ&margin=%5Bobject%20Object%5D&name=image.png&originHeight=41&originWidth=362&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2729&status=done&style=none&taskId=ubd517d70-3915-48ce-bc9e-83580842fde&title=&width=362)
  2. > 提示:
  3. > webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve server 中,就好像它们是挂载在 server 根路径上的真实文件一样。
  4. <a name="fhH1O"></a>
  5. #### ① static
  6. `static: path.resolve(__dirname, './dist')`告知 webpack devServer,将 dist 目录下的文件作为 web 服务的根目录,修改代码后实现浏览器热更新(结合 npx webpack-dev-server 命令开启本地服务器一起使用)
  7. <a name="OvXMr"></a>
  8. #### ② compress
  9. 是否开启`gzip`压缩,对应静态资源请求的响应头里的`Content-Encoding: gzip`。保证从服务器传递给浏览器的文件是压缩的,提高传输效率。不开启的话,则从服务器传递给浏览器的文件是不压缩的<br /> ![](https://cdn.nlark.com/yuque/0/2022/png/2158318/1642865055613-cb1a6918-e7b6-48eb-90a0-40bad136bb65.png#crop=0&crop=0&crop=1&crop=1&from=url&id=xq0EM&margin=%5Bobject%20Object%5D&originHeight=259&originWidth=566&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  10. <a name="jil2j"></a>
  11. #### ③ port
  12. 配置服务的端口号
  13. 以上是`DevServer`的基本配置,除此以外,`devServer.proxy`基于强大的中间件`http-proxy-middleware`实现的,因此它支持很多的配置项,我们基于此,可以做应对绝大多数开发场景的定制化配置:
  14. <a name="kWhXi"></a>
  15. #### ④ headers
  16. 有些场景需求下,我们需要为所有响应添加 headers(响应头),来对资源的请求和响应打入标志,以便做一些安全防范,或者方便发生异常后做请求的链路追踪<br /> ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2158318/1642866188478-4dffb68c-c7c1-4bbc-a945-a8579fc2b66b.png#clientId=ud0c4ad82-c1e1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=331&id=u0eb07392&margin=%5Bobject%20Object%5D&name=image.png&originHeight=331&originWidth=534&originalType=binary&ratio=1&rotation=0&showTitle=false&size=28117&status=done&style=none&taskId=u93f29542-58cc-4932-a442-55d5da7ed31&title=&width=534)
  17. <a name="aSUfQ"></a>
  18. #### ⑤ 开启代理 -- proxy
  19. 我们打包出的`js bundle`里有时会含有一些对特定接口的网络请求 (ajax/fetch) 。要注意,此时客户端地址是在 [http://localhost:3000/](http://localhost:3000/) 下,假设我们的接口来自 [http://localhost:4001/](http://localhost:4001/) ,那么毫无疑问,此时控制台里会报错并提示你跨域。 如何解决这个问题? 在开发环境下,我们可以使用`devServer`自带的`proxy`功能:
  20. ```javascript
  21. module.exports = {
  22. //...
  23. devServer: {
  24. proxy: {
  25. '/api': {
  26. target: 'http://localhost:4001'
  27. }
  28. }
  29. }
  30. }

现在,对 /api/users 的请求会将请求代理到 http://localhost:4001/api/users 。 如果不希望传递 /api,则需要重写路径:

  1. '/api': {
  2. target: 'http://localhost:4001',
  3. pathRewrite: { '^/api': ' ' },
  4. }

默认情况下,将不接受在 HTTPS 上运行且证书无效的后端服务器。 如果需要,可以这样修改配置:

  1. '/api': {
  2. target: 'http://localhost:4001',
  3. pathRewrite: { '^/api': ' ' },
  4. secure: false
  5. }

⑥ https

如果想让我们的本地 http 服务变成 https 服务,只需要这样配置:

  1. module.exports = {
  2. //...
  3. devServer: {
  4. proxy: {
  5. '/api': {
  6. target: 'http://localhost:4001'
  7. }
  8. },
  9. https: true
  10. }
  11. }

注意:此时我们访问 http://localhost:port 是无法访问我们的服务的,我们需要在地址栏里加前缀:https注意:由于默认配置使用的是自签名证书,所以有得浏览器会告诉你是不安全的,但我们依然可以继续访问它

⑦ http2

如果想要配置 http2( http2 默认自带 https 自签名证书,所以我们仍然可以通过 https 配置项来使用自己的证书),那么直接设置:

  1. module.exports = {
  2. //...
  3. devServer: {
  4. proxy: {},
  5. // https: true,
  6. http2: true
  7. }
  8. }

⑧ historyApiFallback

当我们开发一个 SPA (单页面)应用时,如果使用的路由模式为 history,那么当我们刷新页面的时候会报错。例如当路由到 /some 时(可以直接在地址栏里输入),会发现此时刷新页面后,控制台报错。

GET http://localhost:3000/some 404 (Not Found)

此时打开 network,刷新并查看,就会发现问题所在——浏览器把这个路由当作了静态资源地址去请求,然而我们并没有打包出 /some 这样的资源,所以这个访问无疑是404的。 如何解决它? 这种时候,我们可以通过配置来提供页面代替任何404的静态资源响应:

  1. module.exports = {
  2. //...
  3. devServer: {
  4. historyApiFallback: true
  5. }
  6. }

此时重启服务刷新后发现请求变成了 index.html

⑨ host

开发服务器主机。如果你在开发环境中起了一个 devServe 服务,并期望其他人也能访问到它,你只需要配置:

  1. module.exports = {
  2. //...
  3. devServer: {
  4. host: '0.0.0.0'
  5. }
  6. }

这时候,如果其他人跟你处在同一局域网下,就可以通过局域网ip来访问你的服务了
image.png

⑩ HMR

模块热替换(HMR - hot module replacement) 功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。换言之,就是实现页面的部分刷新

启用 webpack 的 热模块替换 特性,需要配置devServer.hot参数:

  1. module.exports = {
  2. //...
  3. devServer: {
  4. // 注:webpack 默认 hot: true
  5. hot: true
  6. }
  7. }
  1. CSS HMR:

HMR 加载 CSS,如果你配置了style-loader,那么现在已经同样支持样式文件的热替换功能了。这是因为style-loader的实现使用了module.hot.accept,在 CSS 依赖模块更新之后,会对 style 标签打补丁,从而实现了这个功能。

  1. module.exports={
  2. module: {
  3. rules: [
  4. {
  5. test: /\.css$/,
  6. use: ['style-loader', 'css-loader']
  7. }
  8. ]
  9. }
  10. }
  1. JS HMR:

除了需要配置devServer.hot参数外,还要使用module.hot.accept。例:

style.css:

  1. .squre {
  2. height: 50px;
  3. width: 50px;
  4. background-color: red;
  5. margin-bottom: 10px;
  6. }

input.js:

const ipt = document.createElement('input')
ipt.placeholder = '0001'
document.body.appendChild(ipt)

index.js:

import '../css/style.css'
import './input.js'

const outDiv = document.createElement('div')
const btn = document.createElement('button')
btn.innerHTML = '点击添加DIV'
outDiv.appendChild(btn)
btn.addEventListener('click', () => {
    const inDiv = document.createElement('div')
    inDiv.classList.add('squre')
    outDiv.appendChild(inDiv)
})
document.body.appendChild(outDiv)

// js 模块实现热替换
if(module.hot) {
    module.hot.accept('./input.js', () => {})
}

⑪ liveReload

热加载:文件更新时,自动刷新我们的服务和页面 。新版的 webpack-dev-server 默认已经开启了热加载的功能。 它对应的参数是devServer.liveReload,默认为 true 。 注意,如果想要关掉它,要将liveReload设置为 false 的同时,也要设置hot: false

module.exports = { 
  //... 
  devServer: {   
    liveReload: false
  }
}

5. eslint

eslint 是用来扫描我们所写的代码是否符合规范的工具。 往往我们的项目是多人协作开发的,我们期望统一的代码规范,这时候可以让 eslint 来对我们进行约束。 严格意义上来说,eslint 配置跟 webpack 无关,但在工程化开发环境中,它往往是不可或缺的。

① 安装 eslint

npm i eslint

② 生成 eslint 配置文件

npx eslint —init

它会帮助我们生成了个配置文件.eslintrc.js(我们自己选择生成 JS 文件),这样我们就完成了eslint 的基本规则配置

运行npx eslint './src',就可以检测代码规范了。

eslint配置文件里的配置项含义如下:

  1. env 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。此处使用的 browser 预定义了浏览器环境中的全局变量,es6 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
  2. globals 脚本在执行期间访问的额外的全局变量。也就是 env 中未预定义,但我们又需要使用的全局变量。
  3. extends 检测中使用的预定义的规则集合。
  4. rules 启用的规则及其各自的错误级别,会合并 extends 中的同名规则,定义冲突时优先级更高。
  5. parserOptions ESLint 允许你指定你想要支持的 JavaScript 语言选项。ecmaFeatures 是个对象,表示你想使用的额外的语言特性,这里 jsx 代表启用 JSX。ecmaVersion 用来指定支持的 ECMAScript 版本 。默认为 5,即仅支持 es5,你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本。你也可以用使用年份命名的版本号指定为 2015(同 6),2016(同 7),或 2017(同 8)或 2018(同 9)或 2019 (same as 10)。上面的 env 中启用了 es6,自动设置了ecmaVersion 解析器选项为 6。 plugins plugins 是一个 npm 包,通常输出 eslint 内部未定义的规则实现。rules 和 extends 中定义的规则,并不都在 eslint 内部中有实现。比如 extends 中的 plugin:react/recommended,其中定义了规则开关和等级,但是这些规则如何生效的逻辑是在其对应的插件react中实现的。