webpack5 文档笔记

238 阅读9分钟

webpack5 的文档笔记

起步

  1. 安装需要的webpack
 yarn add webpack webpack-cli -D
  1. 新建配置文件 webpack.config.js 添加内容
const path = require('path')
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname,'dist')
  }
}

资源管理配置

  1. 加载css 首先下载需要的loader
    yarn add style-loader css-loader -D 并添加规则如下
const path = require('path')
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname,'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
}
  1. 加载images 图像 在 webpack 5 中,可以使用内置的 Asset Modules,我们可以轻松地将这些内容混入我们的系统中
const path = require('path')
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname,'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
    ],
  },
}
  1. 加载fonts 字体 使用 Asset Modules 可以接收并加载任何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文件,也包括字体。让我们更新 webpack.config.js 来处理字体文件:
const path = require('path')
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname,'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
    ],
  },
}

在项目中添加上引入的字体文件

@font-face {
  font-family: 'MyFont';
  src: url('./my-font.woff2') format('woff2'),
    url('./my-font.woff') format('woff');
  font-weight: 600;
  font-style: normal;
}

 .hello {
   color: red;
  font-family: 'MyFont';
   background: url('./icon.png');
 }

管理输出

到目前为止,我们都是在 index.html 文件中手动引入所有资源,然而随着应用程序增长,并且一旦开始 在文件名中使用 hash 并输出 多个 bundle,如果继续手动管理 index.html 文件,就会变得困难起来。然而,通过一些插件可以使这个过程更容易管控

  1. 新增加一个文件,打包出多个文件
export default function printMe() {
  console.log('i name is cg')
}
const path = require('path')
module.exports = {
  mode: 'development',
  entry: {
    index:'./src/index.js',
    print:'./src/js/print.js'
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname,'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
    ],
  },
}

会在输入的时候 输出两个和入口文件名对应的文件 单在index.html 中不会自动更新

  1. 上面的问题 可以使用 HtmlWebpackPlugin 来处理 首先 安装该插件 yarn add html-webpack-plugin -D 然后修改配置
const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
    new HtmlWebpackPlugin({
      title: '管理输出',
      filename: 'index.html',
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      title: '管理输出2',
      filename: 'print.html',
      chunks: ['print']
    }),
  ],

html的插件参数 可以参考

/**
1. title:生成的HTML模板的title,如果模板中有设置title的名字,则会忽略这里的设置
2. filename:生成的模板文件的名字
3. template:模板来源文件(html文件)
4. inject:引入模板的注入位置,取值有true/false/body/head
true:默认值,script标签位于html文件的body底部
body:script标签位于html文件的body底部
head:script标签位于html文件的head中
false:不插入生成的js文件,这个几乎不会用到的
5. favicon:指定页面图标,
然后在生成的html中就有一个link标签:
<link rel='shortcut icon' href='example.ico'>
6.minify:使用minify会对生成的html文件进行压缩,默认是false。html-webpack-plugin内部集成了html-minifier,因此,还可以对minify进行配置,常用的配置项是:
caseSensitive:false,//是否大小写敏感
collapseWhitespace:true//是否去除空格
removeAttributeQuotes:true// 去掉属性引用
removeComments:true,//去注释
7.hash:是否生成hash添加在引入文件地址的末尾,这个可以避免缓存带来的麻烦。默认为true。
8.cache:默认是true的,表示内容变化的时候生成一个新的文件。
9.showErrors::是否将错误信息写在页面里,默认true,出现错误信息则会包裹在一个pre标签内添加到页 面上。
10.chunks::引入的模块,这里指定的是entry中设置多个js时,在这里指定引入的js,如果不设置则默认全部引入。
*/
  1. 清理dist 文件夹

由于遗留了之前的指南和代码示例,我们的 /dist 文件夹显得相当杂乱。webpack 将生成文件并放置在 /dist 文件夹中,但是它不会追踪哪些文件是实际在项目中用到的。

通常比较推荐的做法是,在每次构建前清理 /dist 文件夹,这样只会生成用到的文件。让我们使用 output.clean 配置项实现这个需求。

output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname,'dist'),
    clean: true
  },
  1. manifest

通过 WebpackManifestPlugin 插件,可以将 manifest 数据提取为一个 json 文件以供使用。 在使用 webpack 构建的典型应用程序或站点中,有三种主要的代码类型:

  1. 你或你的团队编写的源码。
  2. 你的源码会依赖的任何第三方的 library 或 "vendor" 代码。
  3. webpack 的 runtime 和 manifest,管理所有模块的交互。

后续待细读

开发环境

  1. 使用source map
  entry: {
    index:'./src/index.js',
    print:'./src/js/print.js'
  },
  devtool: 'inline-source-map',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname,'dist'),
    clean: true
  },

更多的选项 参考 webpack.docschina.org/configurati…

以下选项非常适合开发环境:

eval - 每个模块都使用 eval() 执行,并且都有 //@ sourceURL。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。

eval-source-map - 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。

eval-cheap-source-map - 类似 eval-source-map,每个模块使用 eval() 执行。这是 "cheap(低开销)" 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval devtool。

eval-cheap-module-source-map - 类似 eval-cheap-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。

对于生产环境 这些选项通常用于生产环境中:

(none)(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。

  1. 选择一个开发工具

我们添加一个用于启动 webpack watch mode 的 npm scripts:

"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "watch": "webpack --watch",
   "build": "webpack"
 },

使用 webpack-dev-server
webpack-dev-server 为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。设置如下:

npm install --save-dev webpack-dev-server

修改配置文件,告知 dev server,从什么位置查找文件:

devServer: {
    contentBase: './dist',
  },

我们添加一个可以直接运行 dev server 的 script:

"start": "webpack serve --open",

更多配置项 webpack.docschina.org/configurati…

代码分离

常用的代码分离方法有三种:

入口起点:使用 entry 配置手动地分离代码。 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。 动态导入:通过模块的内联函数调用来分离代码。

  1. 入口起点 在index.js 和 print.js 中都引用相同的库
import _ from 'lodash';

console.log(_.join(['Another', 'module', 'loaded!'], ' '));
entry: {
    index:'./src/index.js',
    print:'./src/js/print.js'
  },
  devtool: 'eval',
  devServer: {
    contentBase: './dist',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname,'dist'),
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: '管理输出',
      filename: 'index.html'
    }),
    
  ],

生成的 index.bundle.js 和 print.bundle.js 都是550K 所以可以看出 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。

  1. 防止重复 SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:
entry: {
    index:'./src/index.js',
    print:'./src/js/print.js'
  },
  devtool: 'eval',
  devServer: {
    contentBase: './dist',
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },

使用 optimization.splitChunks 配置选项之后,现在应该可以看出,index.bundle.js 和 another.bundle.js 中已经移除了重复的依赖模块。需要注意的是,插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。

下面这个配置对象代表 SplitChunksPlugin 的默认行为。

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

更多参数详情 webpack.docschina.org/plugins/spl…

以下是由社区提供,一些对于代码分离很有帮助的 plugin 和 loader:

mini-css-extract-plugin: 用于将 CSS 从主应用程序中分离。

  1. 动态导入(dynamic import)

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 的 import() 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。

在我们开始之前,先从上述示例的配置中移除掉多余的 entry 和 optimization.splitChunks,因为接下来的演示中并不需要它们:

entry: {
    index:'./src/index.js',
    //print:'./src/js/print.js'
  },
  devtool: 'eval',
  devServer: {
    contentBase: './dist',
  },
  // optimization: {
  //   splitChunks: {
  //     chunks: 'all',
  //   },
  // },

index.js 文件如下

function getComponent() {
  const element = document.createElement('div');

  return import('lodash')
   .then(({ default: _ }) => {
     const element = document.createElement('div');

     element.innerHTML = _.join(['Hello', 'webpack'], ' ');

     return element;
   })
   .catch((error) => 'An error occurred while loading the component');
}
getComponent().then((component) => {
  document.body.appendChild(component);
});

由于 import() 会返回一个 promise,因此它可以和 async 函数一起使用。下面是如何通过 async 函数简化代码:

async function getComponent () {
  const {default: _ }} = await import('lodash')
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 return element;
}
 getComponent().then((component) => {
   document.body.appendChild(component);
 });
Tip

我们之所以需要 default,是因为 webpack 4 在导入 CommonJS 模块时,将不再解析为 module.exports 的值,而是为 CommonJS 模块创建一个 artificial namespace 对象,更多有关背后原因的信息

  1. 预获取/预加载模块(prefetch/preload module) webpack v4.6.0+ 增加了对预获取和预加载的支持。

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resource hint(资源提示)",来告知浏览器:

prefetch(预获取):将来某些导航下可能需要的资源 preload(预加载):当前导航下可能需要资源 下面这个 prefetch 的简单示例中,有一个 HomePage 组件,其内部渲染一个 LoginButton 组件,然后在点击后按需加载 LoginModal 组件。 LoginButton.js

import(/* webpackPrefetch: true */ './path/to/LoginModal.js');

这会生成 并追加到页面头部,指示着浏览器在闲置时间预取 login-modal-chunk.js 文件 与 prefetch 指令相比,preload 指令有许多不同之处:

preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。 preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。 preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。 浏览器支持程度不同。

下面这个简单的 preload 示例中,有一个 Component,依赖于一个较大的 library,所以应该将其分离到一个独立的 chunk 中。

我们假想这里的图表组件 ChartComponent 组件需要依赖一个体积巨大的 ChartingLibrary 库。它会在渲染时显示一个 LoadingIndicator(加载进度条) 组件,然后立即按需导入 ChartingLibrary:

ChartComponent.js

import(/* webpackPreload: true */ 'ChartingLibrary');

在页面中使用 ChartComponent 时,在请求 ChartComponent.js 的同时,还会通过 请求 charting-library-chunk。假定 page-chunk 体积很小,很快就被加载好,页面此时就会显示 LoadingIndicator(加载进度条) ,等到 charting-library-chunk 请求完成,LoadingIndicator 组件才消失。启动仅需要很少的加载时间,因为只进行单次往返,而不是两次往返。尤其是在高延迟环境下。

未完待续