[object Object]

Webpack配置

2018年12月10日星期一

# 原始案例


// index.js
var dom = document.getElementById("root");

var header = document.createElement("div");
header.innerText = "header";
dom.append(header);

var slider = document.createElement("div");
slider.innerText = "slider";
dom.append(slider);

var content = document.createElement("div");
content.innerText = "content";
dom.append("content");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>·
  <p>这是我们的网页内容</p>
  <div id="root"></div>
  <script src="./index.js"></script>
</body>
1
2
3
4
5

# 模块化改造


// header.js
var header = document.createElement("div");
header.innerText = "header";
dom.append(header);

// slider.js
var slider = document.createElement("div");
slider.innerText = "slider";
dom.append(slider);

// content.js
var content = document.createElement("div");
content.innerText = "content";
dom.append("content");

// 入口文件 index.js
var dom = document.getElementById("root");
new Header();
new Silder();
new Content();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
  <p>这是我们的网页内容</p>
  <div id="root"></div>
  <script src="./javascript/header.js"></script>
  <script src="./javascript/silder.js"></script>
  <script src="./javascript/content.js"></script>
  <script src="./index.js"></script>
</body>
1
2
3
4
5
6
7
8

http 请求数增加,而且不利于维护,思考:如果使用 import呢?


// 入口文件 index.js
import Header from "/javascript/header.js";
import Silder from "/javascript/silder.js";
import Content from "/javascript/content.js";

var dom = document.getElementById("root");
new Header();
new Silder();
new Content();
1
2
3
4
5
6
7
8
9
10
<body>
  <p>这是我们的网页内容</p>
  <div id="root"></div>
  <script src="./index.js"></script>
</body>
1
2
3
4
5

# Webpack核心


最早的时候,webpack是一个js模块打包工具,随着发展,现在webpack可以打包其他资源文件

# 1. entry入口

entry: "./src/index.js";
// 等价
entry: {
  main: "./src/index.js";
}
1
2
3
4
5

# 2. output出口

entry:{
  main:"./src/index.js",
  sub:"./src/sub.js"
},
output: {
    filename: "[name].js", //可以配置占位符名字
    path: path.resolve(__dirname, "dist"),
    publicPath:"http://www.baidu.com" //CDN 资源链接域名注入
},
1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="http://www.baidu.com/main.js"></script>
    <script src="http://www.baidu.com/sub.js"></script>
</head>
<body>
    <div id="root">HTML模板</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3.使用 Loader 打包静态资源

  • loader运行在打包文件之前,只专注于转化文件(transform)这一个领域
  • loader 有先后顺序 ,从下到上,从右到左

# 3.1. 图片资源

url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,会把图片打包成 base6 输入到 bundle.js 里面,最终打包后带来的好处是:可以减少了 http 请求,但是 bundle.js 会变大, 所以最佳使用方式, 针对小文件图片进行 base64 打包

module:{
  rules:[
  {
      test:/\.(jpg|png|gif)$/,
      use:{
          loader:"file-loader",
          options:{
              name:"[name]_[hash].[ext]", //配置占位符
              outputPath: 'images/',
              limit:2048 //如果图片大于2m,会打包到dist目录下, 小于2m直接打包进bundle.js
          }
      }
  }
]
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3.2. CSS样式

# 3.2.1 css-loader

会帮我们分析出几个 css 之间的文件引用关系,最终把 css 代码合并成一个代码片段

如果在JS中导入了css,那么就需要使用 css-loader 来识别这个模块,通过特定的语法规则进行转换内容最后导出,css-loader会处理 import / require() @import / url 引入的内容。

# 3.2.2 style-loader

style-loader 是通过一个JS脚本创建一个style标签,会把css代码片段插入到 head 的 style 内

{
    test:/\.scss$/,
    use:['style-loader','css-loader','sass-loader']
}

//------------从下到上,从右到左---------------

use: [
  {
    loader: "style-loader" // 将 JS 字符串生成为 style 节点
  },
  {
    loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
  },
  {
    loader: "sass-loader" // 将 Sass 编译成 CSS
  }
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3.2.3 添加 css 前缀(autoprefixer)

PostCSS插件 获取一个 CSS 文件,并提供一个 API 来分析和修改它的规则(通过将它们转换为一个抽象语法树)。这个 API 可以被插件用来做很多有用的事情,比如自动查找错误,插入供应商的前缀

autoprefixer 是PostCSS 插件是最流行的功能插件,用来添加供应商前缀,使用来自 Can I Use 的数据。

//postcss.config.js
module.exports = {
  plugins: [
    require("autoprefixer")({
      //配置浏览器厂商,否则前缀无法添加
      browsers: ["Android >= 4.0", "iOS >= 7"]
    })
  ]
};
1
2
3
4
5
6
7
8
9

# 3.2.4 模块化css解决样式冲突

往往修改一个文件会影响另外的样式,所以我们需要模块化的 css

 {
    loader: 'css-loader',
    options: {
        importLoaders:2,
        modules:true //开启css模块化
    }
},
1
2
3
4
5
6
7
/* index.scss */
body {
    .avatar{
        width: 150px;
        height:150px;
        transform: translate(100px,100px);
    }
}
1
2
3
4
5
6
7
8

// index.js
import style from "./index.scss";
var img = new Image();
img.src = avatar;
img.classList.add(style.avatar);
document.getElementById("root").append(img);
1
2
3
4
5
6
7

# 3.2.5 字体打包

 {
    test: /\.(woff|woff2|eot|ttf|otf)$/,
    use: [
     'file-loader'
    ]
}
1
2
3
4
5
6

# 4.Plugins 让打包更便捷

plugin 可以在 webpack 运行的某一时刻为你做某些事情

plugin是为了扩展webpack的功能,plugin 是作用于webpack本身上的,plugins在整个编译周期都起作用。而且plugin不仅只局限在打包,资源的加载上,它的功能要更加丰富。从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务

# 4.1.htmlwebpackplugin

新增 template 模板文件,写好 root 节点之后,webpack 会把 src/index.html 打包进 dist/index.html 格式中

plugins: [
  new HtmlWebpackPlugin({
    template: "./index.html"
  })
];
1
2
3
4
5

# 4.2.clean-webpack-plugin

用于删除/清理生成文件夹的网页包插件

const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // 引入
// .......
plugins: [
  new HtmlWebpackPlugin({
    template: "./src/index.html"
  }),
  new CleanWebpackPlugin() // 使用
];
1
2
3
4
5
6
7
8

# 模式


开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置

简单说 Sourcemap 构建了处理前以及处理后的代码之间的一座桥梁,方便定位生产环境中出现 bug 的位置

  • 开发环境会生成 sourceMap 方便我们调试,但是线上 sourceMap 会生成.map 文件保存
  • 开发环境代码不压缩,生成环境代码会压缩
  • 开发环境,配合 webpack-dev-server和HMR
npm install webpack-merge --save-dev
1
"dev": "webpack-dev-server --config webpack.dev.js",
"prod": "webpack --config webpack.prod.js",
1
2
//webpack.dev.js----
const webpack = require("webpack");
const commonConfig = require("./webpack.common");
const merge = require("webpack-merge");
const devConfig = {
  mode: "development", //开发环境进行打包,代码不会压缩
  devtool: "cheap-module-eval-source-map", //使用scurceMap
  plugins: [new webpack.HotModuleReplacementPlugin()],
  // package.json中配置dev命令
  devServer: {
    contentBase: "./dist",
    open: true, //自动打开一个localhost:8080地址的浏览器地址
    port: 8080, //指定要监听请求的端口号:
    hot: true, //开启 HotModuleReplacement
    // hotOnly: true, //即使HRM不生效,出现问题, 浏览器也不刷新
    proxy: {
      "/api": "http://localhost:3000",
    },
  },
  optimization: {
    usedExports: true,
  },
};
module.exports = merge(commonConfig, devConfig);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 环境变量的使用方法


# 1. 分离文件

原有的方法通过不同的配置文件进行配置,然后进行打包

// ——————————————————————————————————webpack.common.js
module.exports = {
  // 公共的配置,dev和prod环境都需要
};


// ——————————————————————————————————webpack.dev.js
const commonConfig = require("./webpack.common");
const merge = require("webpack-merge");
const devConfig = {
  // 开发环境的主要配置
};
module.exports = merge(commonConfig, devConfig);


// ——————————————————————————————————webpack.prod.js
const commonConfig = require("./webpack.common");
const merge = require("webpack-merge");
const prodConfig = {
  // 开发环境的主要配置
};
module.exports = merge(commonConfig, prodConfig);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

package.json 如下配置

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "watch": "webpack --watch",
    "dev-build": "webpack --config ./build/webpack.dev.js",
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "prod": "webpack --config ./build/webpack.prod.js",
    "middleware": "node server.js"
  },
1
2
3
4
5
6
7
8
9

# 2. 利用环境变量

现在也可以通过一个webpack.common.js文件进行配置环境变量进行打包

// webpack.dev.js-----------------------------------------
const devConfig = {
  mode: "development", //开发环境进行大打包,代码不会压缩
};
module.exports = devConfig;

// webpack.prod.js---------------------------------------
const prodConfig = {
  mode: "production",
};
module.exports = prodConfig;


//webpack.common.js 中进行判断----------------------------

const merge = require("webpack-merge");
const prodConfig = require("./webpack.prod.js");
const devConfig = require("./webpack.dev.js");
const commonConfig = {
  //dev和prod的主要公共配置
  // ....
};
module.exports = (env) => {
  if (env && env.production===abcd) {
    return merge(commonConfig, prodConfig);
  } else {
    return merge(commonConfig, devConfig);
  }
};
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

package.json配置

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "watch": "webpack --watch",
    "dev-build": "webpack --config ./build/webpack.common.js",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "prod": "webpack --env.production=abcd --config ./build/webpack.common.js",
    "middleware": "node server.js"
  },
1
2
3
4
5
6
7
8
9

# SourceMap(映射)


sourceMap 是一个映射关系,代码出错可以映射到原本我们书写的代码上

devtool此选项控制是否生成,以及如何生成 source map。

module.exports = {
  devtool: "source-map"
  // ....
};
1
2
3
4
  • source-map希望代码写错的时候,我们需要直接找到源代码哪里出错,
  • inline-source-map map文件不会出现在 dist 目录下,而是会被通过 base64 方式打包到 js 文件中
  • cheap-module-eval-source-map,开发环境中可使用
  • cheap-module-source-map,生产环境的代码,不需要 sourceMap. 但是我们如果需要定位问题的话,可以使用

# WebpackDevServer


# 配置

 devServer:{
      contentBase:"./dist",
      open:true,  //自动打开一个localhost:8080地址的浏览器地址
      port: 8080, //指定要监听请求的端口号:
      proxy: {
          "/api": "http://localhost:3000"
        }
  }
1
2
3
4
5
6
7
8

# 扩展

比较早的时候,devServer 不稳定,有些框架作者自己写 server 服务器

const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpackConfig = require("./webpack.config"); // 自己配置的
const complier = webpack(webpackConfig); //编译器
const app = express();

app.use(
  webpackDevMiddleware(complier, {
    publicPath: webpackConfig.output.output
  })
);

app.listen(3000, () => {
  console.log("服务器启动运行: 3000端口");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# HMR热模块替换


在HMR之前,大多数开发体验是live reload,保存后自动刷新浏览器,

现在,HMR大幅提高了开发体验,只更新变更内容,调整样式迅速,避免了大部分的网络请求、浏览器重新渲染

我们就是期望有一种手段是当我们修改样式文件的时候,就像是我们在控制台中操作一样,当前的页面不会变,但是样式却生效。这时候我们就可以借助HMR

const webpack = require('webpack');
plugins:[
  new webpack .HotModuleReplacementPlugin()
],
devServer:{
      contentBase:"./dist",
      open:true,  //自动打开一个localhost:8080地址的浏览器地址
      port: 8080, //指定要监听请求的端口号:
      hot:true, //开启 HotModuleReplacement
      proxy: {
          "/api": "http://localhost:3000"
        }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 对 css 的影响

修改 css 文件时,只会替换修改的 css 文件,而不会整体刷新,方便调试 css

我们修改 css,css-loader 已经帮我们编写完毕了,底层已经实现,vue-loader 里面也帮我们内置了这种 HMR 实现

# 对 js 的影响

同样是打开HMR,如果修改了Js代码是不会自动改变的。webpack为我们提供了很多关于HMR的接口,让我们自行实现热更新的逻辑。为了解决js热更新的这个问题,我们需要对文件的改变做出一个监听,同时给予监听的逻辑。


if (module.hot) {
    // 如果开启了HRM,监听number文件变化,这时候重新执行
    module.hot.accept("./number", () => {
      console.log("哈哈哈")
      // 删除老的dom节点
      document.body.removeChild(document.getElementById("number"));
      number();
    });
}
1
2
3
4
5
6
7
8
9
10

在一些高级的框架中,比如Vue,vue-loader已经帮我们写好了module.hot.accept的部分了

# babel 处理 es6 语法

npm install --save-dev babel-loader @babel/core

  • @babe/core 是 babel 核心库,可以让 webpack 识别 js 代码,生成抽象语法树
module: {
  rules: [{ 
    test: /\.js$/, 
    exclude: /node_modules/, 
    loader: "babel-loader" 
  }];
}
1
2
3
4
5
6
7

npm install @babel/preset-env --save-dev`

  • babel-loader 只是 webpack 和 babel 之间的通信桥梁, 并不会将 es6 语法翻译成 es5, 我们还需要@babel/preset-env模块进行翻译
 {  
   test: /\.js$/,
    exclude: /node_modules/,
    loader: "babel-loader",
    options:{
      "presets": ["@babel/preset-env"]
    }
 }
1
2
3
4
5
6
7
8

npm install --save @babel/polyfill

  • promise,async等这种语法,在低版本浏览器中还是没有,我们不管需要语法转换,我们还要将这些函数等在低版本浏览器中进行补充
import "@babel/polyfill";
const arr = [new Promise(() => {}), new Promise(() => {})];
arr.map(item => {
  console.log(item);
});
1
2
3
4
5
{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: "babel-loader",
  options: {
      "presets": [
          ["@babel/preset-env", {
              "useBuiltIns": "usage" 
         // 根据业务代码进行补充,否则不配置的话,打包出来的文件体积变大
          }]
      ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Tree Shaking

通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。

tree shaking 只支持 ES Module,因为 ES Module 是静态引入代码模块的,而 CommonJS 是动态引入的

webpack的treeshking是基于 es module的静态分析,能够在编译期间就确定哪些模块用到了哪些模块没用到,并且配合解构赋值还能确定哪些export用到了,哪些export没用到。然后对用到的部分和没用到的部分进行标记,在压缩阶段就可以删除标记出的没有用到的部分,从而达到treeshking的目的。

1.development(开发模式)下是没有 Tree Shaking功能的, 如果需要 Tree Shaking我们可以添加配置optimization.usedExports来标记使用和未使用的模块

optimization: {
  usedExports: true;
}
1
2
3

2.将文件标记为无副作用,因为有副作用的代码不能treeshking,只能对export进行treeshking。

在一个纯粹的 ESM 模块世界中,识别出哪些文件有副作用很简单。然而,我们的项目无法达到这种纯度,所以,此时有必要向 webpack 的 compiler 提供提示哪些代码是“纯粹部分”:

比如在window上挂一个变量、写本地文件等,这种代码虽然没有export一些内容,但也是不能被treeshking掉的。对于这些文件需要过滤掉,配置的方式就是在package.json中添加sideEffects字段,因为webpack的模块包括图片、字体文件、css文件等,这些模块都是需要配置的。

// package.json
"sideEffects":false // 不需要@babel/polyfill的情况下,设置为false,意味着: 正常对所有模块进行Tree Shaking处理

//或者,
"sideEffects":["@babel/polyfill"] // 忽略对"@babel/polyfill"的Tree Shaking 处理
1
2
3
4
5

3.注意:开发环境下即使开启了Tree Shakingwebpack 也不会把没有使用的模块代码去除掉,会保留代码,因为我们开发环境下还需要进行 SourceMap 调试,但是我们如果上线时mode:production,Tree Shaking 就会生效,需要修改配置

mode: "development", //开发环境进行大打包,代码不会压缩
devtool: "cheap-module-source-map", //使用scurceMap
optimization:{
  usedExports:false // 开发环境不需要tree shaking
}
1
2
3
4
5

# Code Splitting

import _ from "lodash"; // 1M
console.log(_.join([1, 2, 3]));
//此处省略.....
console.log(_.join([1, 2, 3]));
1
2
3
4
  • 打包文件很大,用户打开页面需要加载这么大的 js 文件,导致访问时间变长

  • 最重要的是如果我们修改业务代码后,webpack 又会重新打包生成 main.js,用户又需要重新加载这么大的 js 文件

借助 webpack 配置,进行同步代码的分割,只需要在webpack.common.js中做 optimization的配置

// webpack.common.js
optimization: {
  //做代码分割
  splitChunks: {
    chunks: "all";
  }
}
1
2
3
4
5
6
7

# 1. SplitChunksPlugin

实际上 webpack 打包时,底层使用了 SplitChunksPlugin 这个插件,我们可以给他配置很多参数来完成不同的功能

魔法注释:webpack 会把分离出来的代码文件名自动设置为0.js,我们如果需要添加指定名称,那么就需要些如下注释

function getComponent() {
  return import(/* webpackChunkName:"lodash"*/"lodash").then(
    ({ default: _ }) => {
      var element = document.createElement("div");
      element.innerHTML = _.join(["cc", "hh"], "-");
      return element;
    }
  );
}

getComponent().then((element) => {
  document.body.appendChild(element);
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. CSS分割

# mini-css-extract-plugin

npm install --save-dev mini-css-extract-plugin

一般在线上环境中进行使用,开发环境依然使用 style-loader,线上环境修改为这个插件提供的 loader。webpack.common.js 中将 css 配置部分剪切为两份,

  • 一份是开发环境 webpack.dev.js 使用 style-loader
  • 一份是生产环境 webpack.prod.js 使用 MiniCssExtractPlugin.loader 如下
// webpack.prod.js
const commonConfig = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const merge = require("webpack-merge");
const prodConfig = {
  mode: "production", //开发环境进行大打包,代码不会压缩
  devtool: "cheap-module-source-map", //使用scurceMap
  plugins: [],
  modules: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              importLoaders: 2,
            },
          },
          "sass-loader",
          {
            loader: "postcss-loader",
            options: {},
          },
        ],
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
    ],
  },
};
module.exports = merger(commonConfig, prodConfig);
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

# optimize-css-assets-webpack-plugin

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
  optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin({})],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 所有入口文件提取合并一个 css

假如页面有多个入口文件index1,index2,index3.js..,希望所有引入的 css 文件都打包生成到一个 css 文件中,

与 extract-text-webpack-plugin 类似,可以使用 optimization.splitChunks.cacheGroups 将 CSS 提取到一个 CSS 文件中。

mini-css-extract-plugin底层也是要借助 splitChunks,所以我们可以额外增加一个组,只要发现打包的文件是 css 后缀。不管是同步还是异步文件,都将打包到一个 styles 文件中去

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: "styles",
          test: /\.css$/,
          chunks: "all",
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};
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

还可以把所有入口文件各自提取合并一个 css。假如页面有多个入口文件index1,index2,index3.js..,希望 index1.js 引入的 css 文件都打包生成到一个 css 文件中,index2.js 引入过的 css 文件都打包到另一 css 文件中,依次类推....

# Lazy Loading懒加载

主要是 es 中的 import 实验性质语法,懒加载和 webpack 关系不是很大,主要是 webpack 能识别这种 import 语法,然后对他引入的模块进行代码分割而已

通过 import 异步的加载某一个模块,如下,实际上是在代码真正执行的时候才载入模块

function getComponent() {
  return import(/* webpackChunkName:"lodash"*/ "lodash").then(
    ({ default: _ }) => {
      var element = document.createElement("div");
      element.innerHTML = _.join(["111", "222"], "-");
      return element;
    }
  );
}
//只有在点击页面时才会加载上面的代码模块
document.addEventListener("click", () => {
  getComponent().then((element) => {
    document.body.appendChild(element);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

页面加载速度更快,不需要首次加载,比如 vue 等框架中的路由,页面跳转时会展示页面的不同组件

注意:import 语法返回一个 promise,所以项目必须使用 babel-polyfill 进行向下兼容 promise 语法

# 打包分析

# 1. 官方工具

- "dev-build": "webpack --config ./build/webpack.dev.js",
+  "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
1
2

npm run dev-build 就会生成 stats.json 文件,然后进入http://webpack.github.io/analyse

# 2. 第三方插件

webpack-bundle-analyzer

# 提升 webpack 打包速度的方法

# 1. 对loader作用范围做限制

 {
    test: /\.js$/,
    exclude: /node_modules/,
    use:[{
      loader: "babel-loader"

    },{
      loader: "imports-loader?this=>window"
    }]
 }
1
2
3
4
5
6
7
8
9
10

也可以 include,只有 src 下的代码才被打包编译

 {
    test: /\.js$/,
    include:path.resolve(__dirname,'../src'),
    use:[{
      loader: "babel-loader"

    },{
      loader: "imports-loader?this=>window"
    }]
 }

1
2
3
4
5
6
7
8
9
10
11

# 2. Plugin使用

  • 尽可能少用 plugin 并确保 plugin 可靠性

  • 开发环境下不需要对代码压缩,所以在开发环境下就不需要配置这种 plugin,减少打包速度

  • 使用 webpack 官方的插件,性能有保障,或者社区认可的插件

# 3. resolve参数合理配置

在代码中通过 import 导入时可以省略文件后缀,我们上面配置时只需要配置 js,jsx 这些逻辑文件,而 css,jpg 等等静态资源最好不要配置,直接显示导入资源文件显示写后缀

extensions配置这个过程是需要Node.js尝试去读取文件实现的,这个过程是非常耗费性能的。

  resolve:{
    extensions:['.js','.jsx']
  },
1
2
3

# 4. DllPlugin提高打包速度

在用 Webpack 打包的时候,对于一些不经常更新的第三方库,比如 react,lodash,vue 我们希望能和自己的代码分离开

Dll这个概念是借鉴了Windows系统的dll,一个dll包,就是一个纯纯的依赖库,它本身不能运行,是用来给你的app引用的。

打包dll的时候,Webpack会将所有包含的库做一个索引,写在一个manifest文件中,而引用dll的代码(dll user)在打包的时候,只需要读取这个manifest文件

配置 webpack.dll.js 进行第三方模块的单独打包生一个结果, 暴露一个全局变量,然后借助 DllPlugin 创建对代码进行分析生成一个映射文件vendors.manifest.json和第三方库的源代码集合文件,然后 build 时,就不用去 node_modules 里面去寻找,这时我们需要 DllReferencePlugin 插件结合映射文件进行分析, 就节省了很多打包时间

# 生成映射文件(构建出动态链接库文件)

//webpack.dll.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
  mode: "production",
  entry: {
    vendors: ["lodash"],
    react: ["react", "react-dom"]
  },
  output: {
    path: path.resolve(__dirname, "../dll"),//path是manifest文件的输出路径
    filename: "[name].dll.js", //name是dll暴露的对象名
    library: "[name]"
  },
  plugins: [
    new webpack.DllPlugin({
      name: "[name]",
      path: path.resolve(__dirname, "../dll/[name].manifest.json")
    })
  ]
};

// webpack.common.js
 plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.ProvidePlugin({
      $: "jquery"
    }),
    new AddAssetHtmlPlugin({
      filepath: require.resolve("../dll/vendors.dll.js")
    }),
    new AddAssetHtmlPlugin({
      filepath: require.resolve("../dll/react.dll.js")
    }),
    // 哪些动态链接库
    new webpack.DllReferencePlugin({
      // 动态链接库的文件内容
      manifest: require.resolve("../dll/vendors.manifest.json")
    }),
    new webpack.DllReferencePlugin({
      //动态链接库的文件内容
      manifest: require.resolve("../dll/react.manifest.json")
    })
  ],
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

# 5. 控制包文件大小

  • 用不到的包 tree-shaking 去除掉,也可以不引入
  • split-chunk-plugin 将大文件拆分成多个小文件

# 6. 合理使用 sourceMap

生成 sourceMap 越详细,打包速度就越慢

需要考虑的问题:不同环境打包的时候,什么样的 sourceMap 是最合适的?

# 7.多进程打包 happypack

Last Updated: 最后更新 2022-12-15 23:46:19