webpack高级篇(三)

1,212 阅读10分钟

五、webpack实战配置案例详解

1. Library的打包

我们要开发一个组件库或函数库的时候,如何通过webpack打包成npm包呢?

新建library文件夹,运行npm init -y

npm init -y
// package.json

{
  "name": "library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "Fruit Bro",
  "license": "MIT",
  "dependencies": {
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11"
  }
}
npm install webpack webpack-cli --save
// webpack.config.js

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js',
    ibraryTarget: 'umd', // 通用,不管任何模式引入import、require、script标签等,都可以正确引入到库
  }
}

代码如下:

// index.js

import * as math from './math'
import * as string from './string'

export default { math, string }
// math.js

export function add(a, b) {
  return a + b
}

export function minus(a, b) {
  return a - b
}

export function multiply(a, b) {
  return a * b
}

export function division(a, b) {
  return a / b
}
// string.js

export function join (a, b) {
  return a + ' ' + b
}
npm run build

打包完成,我们该如何使用呢?

// 引入方式
import library from 'library'
const library = require('library')
require(['library'], function () {
    
})

<script src='library.js'></script>


library.math想要以这种方式使用library需要的配置如下:

// webpack.config.js

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js',
    library: 'library', // 这样就可以用library调用,打包生成的代码挂载到了页面的全局变量上
    libraryTarget: 'umd',
  }
}

在页面上运行

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src='./library.js'></script>
</body>
</html>

librarylibraryTarget是我们做库打包是经常要配置的两个参数

library: 'library',
libraryTarget: 'this' // 在全局的this上挂载了library,通过this.library可以获取到
libraryTarget: 'window' // 或在nodejs中用global

当我们希望我们的库中引入别的第三方库的时候,如lodash,写法如下:

npm install lodash --save
// string.js

import _ from 'lodash'

export function join (a, b) {
    return _.join([a, b], '')
}
npm run build

此时,生成的包变为了72.6KB,别人引用的时候也有可能会引入lodash,如下:

import _ from 'lodash'
import library from 'library'

此时需要做如下配置:

// webpack.config.js

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  externals: ['lodash'], // 打包的时候,如果遇到了lodash,直接忽略这个库。可以是数组也可以是对象
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js',
    library: 'library', 
    libraryTarget: 'umd', // 常用配置,一般为umd
  }
}

参考文档: webpack.docschina.org/configurati…

重新打包

npm run build

此时生成的包,又重新变为了1.64 KiB,此时该如何使用呢?用法如下:

import _ from 'lodash'
import library from 'library'

externals用法

externals: {
    lodash: {
        root: '_', // 以script标签的方式引入,以_的方式注入全局变量,一般不用
        commonjs: 'lodash' // 库在commonjs环境下被使用
    }
}

写法如下

const lodash = require('lodash')  // 以require的方式引入
const library = require('library')
// package.json 修改入口

{
  "name": "library",
  "version": "1.0.0",
  "description": "",
  "main": "./dist/library.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "Fruit Bro",
  "license": "MIT",
  "dependencies": {
    "lodash": "^4.17.15",
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11"
  }
}

把包发布到 npm

  1. 先在npm上注册账号
  2. 在项目的命令行中npm adduser添加用户名和密码
  3. 运行npm publish

这样其他人就可以通过npm install的方式安装并使用了

2. PWA的打包配置

npm install workbox-webpack-plugin --save-dev
// webpack.prod.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const workboxPlugin = require('workbox-webpack-plugin')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const prodConfig =  {
    mode: 'production',
    devtool: 'cheap-module-source-map',
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader,
              options: {
                publicPath: '../'
             }
            },
            "css-loader"
          ]
        }
      ]
    },
    plugin: [
      new MiniCssExtractPlugin({
          filename: '[name].css',  // 被页面直接引用,走这个名字
          chunkFilename: '[name].chunk.css',
      }),
      new workboxPlugin.GenerateSW({ // SW: service worker
          clientsClaim: true,
          skipWaiting: true, // 还有其他很多配置项
      }),
      new OptimizeCssAssetsPlugin({
        assetNameRegExp: /\.optimize\.css$/g,
        cssProcessor: require('cssnano'),
        cssProcessorPluginOptions: {
          preset: ['default', { discardComments: { removeAll: true } }],
        },
        canPrint: true
      })
    ]
}

module.exports = merge(commonConfig, prodConfig)
npm run build

运行后会多出两个文件:1. service-worker.js 2. precache-mainfest.***.js

// index.js

if ('serviceWorker' in navigator) { // 如果浏览器支持serviceworker
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js')
          .then(registration => { // 注册成功
              console.log('service-worker registed')
          }).catch(error => {
              console.log('service-worker registed error')
          })
    })
}

重新打包代码,重启服务器后,访问页面,然后关闭服务器,刷新页面,依旧可以正常访问。关于PWA的配置项还有很多。

3. TypeScript 的打包配置

规范了我们的代码,提升代码的可维护性,

  1. 创建文件夹 type-script
  2. npm init -y
  3. 创建webpack.config.js
  4. 新建src文件夹,新建index.tsx
npm install webpack webpack-cli --save-dev

中文官网: www.tslang.cn/

TypeScriptJavaScript的超集.

www.tslang.cn/play/index.…

// index.tsx

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function() {
    alert(greeter.greet());
}

document.body.appendChild(button);
// webpack.config.js

const path = require('path')
module.exports = {
    mode: 'production',
    entry: './src/index.tsx',
    module: {
        rules: [{
            test: '/\.tsx?$/',
            use: 'ts-loader',
            exclude: /node_modules/
        }]
    },
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
}
npm install ts-loader typescript --save-dev
{
  "name": "type-script",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bulid": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

安装ts-loader之后,还需要配置tsconfig.json

// tsconfig.json

{
    "compilerOptions": {
        "outDir": "./dist", // 代码打包到dist下
        "module": "es6", // 以import的方式引入模块
        "target": "es5", // 打包生成的目标语法
        "allowJs": true, // 允许引入js文件
    }
}
npm run build

生成bundle.js

引入lodash

npm install lodash --save

没有体现出ts的优势

npm install @types/lodash --save-dev

npm install @types/jquery --save-dev

此时引用需要改为如下写法

import * as _ from 'lodash'

如何知道哪些库可以安装对应的类型文件? github.com/DefinitelyT…

可以在如下网站进行搜索:TypeSearch

总结:

  1. tsjs的超集
  2. ts支持对属性或方法类型的校验
  3. 打包ts语法需要tsconfig.json,tsconfig.json配置了对ts打包的配置项
  4. ts引入外部的库,需要安装这些库的类型文件,如@types/lodash

4. 使用WebpackDevServer实现请求转发

参考文档: webpack.docschina.org/configurati…

devServer: {
    proxy: {
        'react/api': 'http://xxx.com',  // 当请求react/api对应的接口时,就会把路由转发到http://xxx.com下
        target: ''
    }
}

更进一步配置

默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,修改配置如下:secure: false

devServer: {
    proxy: {
        'react/api': {
            target: 'http://xxx.com',
            secure: false, // 对https网址的请求的转发生效
            pathRewrite: {
                'header.json' : 'demo.json' // 如果请求header.json则把demo.json的数据返回,可做为临时接口使用,转发数据,不需要改源代码,就能拿到转发的数据
            },
            bypass: function(req, res, proxyOptions) { // 拦截
              if (req.headers.accept.indexOf('html') !== -1) { // 要接收的是html的内容时,直接返回/index.html的内容
                console.log('Skipping proxy for browser request.');
                return '/index.html';
              }
            },
            index: '', // 跟路由的代理转发,不能直接用'/'
            changeOrigin: true, // 后端对origin做了限制,解决对origin的限制
            headers: { // 在请求头中自定义内容,模拟登陆行为
                host: 'www.xxx.com'
            }
        },  // 
    }
}

webpackdevserverproxy

// 多路径的写法
proxy: [{
    context: ['/auth', '/api'], 
    target: 'http://localhost:3000',
}],

参考文档: webpack.docschina.org/configurati…

从上述文档可以看出,实际上webapckdevserver底层用了http-proxy-middleware这个库。只会在开发环境生效。

完全可以替代掉charles等代理工具。

5. WebpackDevServer解决单页面应用路由问题

npm install react-router-dom  --save
import React, { Component } from 'react'
import { BrowserRouter, Route } from 'react-router-dom'
import ReactDom from 'react-dom'
import Home from './home.js'
import List from './list.js'

class App extends Component {
  render () {
    return (
      <BrowserRouter>
        <div>
          <Route path='/' exact component={Home} />
          <Route path='/list' component={List} />
        </div>
      </BrowserRouter>

      )
  }
}

ReactDom.render(<App />, document.getElementById('root'))

此时,虽然写了//list两个路由,但/list路由无法访问到。因为我们没有list.html页面。

参考文档:webpack.docschina.org/configurati…

module.exports = {
  //...
  devServer: {
    historyApiFallback: true // 把所有的路由转换到根路径的请求。可配置内容非常多。这个问题是不能解决线上的问题的,需要在nginx或apache上做相同的配置。
  }
};
module.exports = {
  //...
  devServer: {
    historyApiFallback: {
      rewrites: [ // 通过更具体的规则来控制路由
        { from: /^\/$/, to: '/views/landing.html' },
        { from: /^\/subpage/, to: '/views/subpage.html' },
        { from: /./, to: '/views/404.html' }
      ]
    }
  }
};

此配置底层使用的库如下:

github.com/bripkens/co…

history({
  rewrites: [
    {
      from: /^\/libs\/.*$/, // 访问libs目录
      to: function(context) {
        return '/bower_components' + context.parsedUrl.pathname;
      }
    }
  ]
});

6. ESLint 在webpack中的配置

  1. ESLint是什么
  2. 为什么要在webpack中配置ESLint 你的代码写的是否规范呢?
npm install eslint --save-dev

在项目目录下执行如下命令

npx eslint --init

一般选择业界通用的规范Use a popular style guide,然后会有多种通用模板Airnb、 Standard、 Google,一般选择Airnb

生成了.eslintrc.js

运行

npx eslint src

检测src下的文件

参考文档: eslint.bootcss.com/docs/user-g…

"parser": "esprima",官方默认使用的是esprima,我们使用的是babel-eslint

npm install babel-eslint --save-dev

在编辑器vscode上安装eslint插件。编辑器会结合我们的配置文件自动对不规范的语法标红。

这条规则提示lines-between-class-members,class类之间需要有空行,如果不想使用这条规则,在以在.eslintrc.js中进行如下配置,就不会有上述错误提示了:

// .eslintrc.js
"rules": {
    "lines-between-class-members": 0,
}

如果document提示报错,则进行如下配置:

// .eslintrc.js
"globals": {
    document: false, // document这个全局变量在代码里不允许被覆盖
},

无法保证所有同事都用同一个编辑器,此时可以用webpack中把eslint结合进去,这样不管有没有eslint插件,都可以看到代码的问题。

npm install eslint-loader --save-dev

loader执行顺序,从后到前

// webpack.config.js
rules: [{
    test: /\.js$/,
    exclude: /node_modules/,
    use: ['babel-loader', 'eslint-loader'], // 先用eslint-loader检测对不对,再用babel-loader转化代码
}]

运行时如果代码有错误则报错Module Error (from ./node_modules/eslint-loader/index.js),但报错并不是非常直观,此时我们可以进行如下配置

devServer: {
  overlay: {
    warnings: true,
    errors: true
  },
   cache: true
}
或
devServer: {
  overlay: true
}

则会在页面上直接弹出一个蒙层,提示错误的原因和位置。这样就可以快速定位问题,快速解决问题。

overlay + eslint-loader 就可以做到在页面上提示代码不规范的问题。

参考文档: webpack.js.org/loaders/esl…

// webpack.config.js
rules: [{
    test: /\.js$/,
    exclude: /node_modules/,
    use: ['babel-loader', {
        loader: {
           loader: 'eslint-loader',
           options: {
               fix: true // 会自动帮你解决简单的代码错误
               cache: true, // cache参数可减少eslint对打包代码的性能的损耗
           }
        }
    }], 
}]

cache参数可减少eslint对打包代码的性能的损耗。

force: pre 强制某个loader优于其他loader,先执行。

最佳实践: 一般不会把eslint-loader配置在webpack中,因为这样会降低打包速度。一般会配置在git的钩子中,在提交代码的时候,对代码进行eslint的检测,在钩子里运行eslint src,如果不规范则会禁止提交,并报出相应的错误,解决完之后才可以进行代码提交。 在你们的项目中,又是如何做的呢?

7. webpack性能优化

  • 提升webpack打包速度的方法

    1. 跟上技术的迭代(Node、Npm、Yarn)。webpack运行在node之上,新版本更新升级webpack、node、npm、yarn的版本。
    2. 在尽可能少的模块上应用Loader。不必要就可以删除。
loader: 'imports-loader?this=>window' // 一般不会这么做,可删除

new webpack.ProvidePlugin({
	$: 'jquery',  // 较少用,可删除
	_join: ['lodash', 'join']
}),


optimization: {
	runtimeChunk: {
		name: 'runtime'
	},
	usedExports: true, // 
	splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/, 
          priority: -10,
          name: 'vendors',
        }
      }
    }
},

rules: [{ 
	test: /\.js$/, 
	exclude: /node_modules/,  // node_modules下的代码已经打包编译过了。或者写成 include: path.resolve(__dirname, '../src')
	use: [{
		loader: 'babel-loader'
	}]
}]
// 图片因为都需要做处理,因此就没有必要配置exclude或include了。

合理的使用excludeinclude可以降低loader的执行频率,从而提高打包速度。

  1. Plugin尽可能少的使用,同时确保Plugin的可靠性

一般用官方推荐的插件,经过了官方的性能验证。

optimization: {
	minimizer: [new OptimizeCSSAssetsPlugin({})] // 对css代码进行压缩,在开发环境时不需要对代码进行压缩。
},

(1). 插件要合理的使用,不要使用冗余的没有意义的插件。

(2).选择性能比较好的,官方认可的插件。

  1. resolve参数合理配置
test: /\.jsx?$/ , // ?表示x可有可无,js和jsx都会走babel-loader
module.export = {
  resolve: {
    extensions: ['.js', '.jsx'] // 当引入某个文件的时候,先找以js、jsx结尾的文件。配置resolve之后,就不会有编译之后无法解析的问题了。不要滥用,如写.png .css,因为每次文件的查找,都会调用node底层调用文件的操作。
  }
}

当引入某个文件的时候,先找以js、jsx结尾的文件。配置resolve之后,就不会有编译之后无法解析的问题了。不要滥用,如写.png .css,因为每次文件的查找,都会调用node底层调用文件的操作。因此,一般只有引入js、jsx这类逻辑性文件的时候。才会把他放到extensions这个配置项中。资源类的,如css、png等不建议配置,而是显式的引入。

例如:

import child from './child' // 会优先查找child.js或child.jsx文件
module.export = {
  resolve: {
    extensions: ['.js', '.jsx'],
    mainFiles: ['index', 'child'] // 先找以index开头的文件,再找以child开头的文件
  }
}

如:若child文件夹下包含child.jsx,上面的配置即可生效。这个配置一般不用

import child from './child' 
module.export = {
  resolve: {
    extensions: ['.js', '.jsx'],
    mainFiles: ['index', 'child'],
    alias: {
       '@/src': path.resolve(__dirname, `../src`),  // 当看到@/src这个路径或字符串的时候,实际上指向的是../src目录
    }
  }
}
  1. 使用DllPlugin提高打包速度

(1)创建webpack.dll.js (2)修改webpack.commom.js,引入add-asset-html-wepack-pluginDllReferencePlugin (3) 结合引入的第三方模块,同vendors.manifest.json进行分析

splitChunks配置参数主要作用:将所有的第三方模块(node_modules)中的文件都打包到vendor.js文件中。

vendor: {
  test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
  name: 'vendor',
  chunks: 'initial',
  minChunks: 2
}

优化思路:第三方模块代码一般是不会变的。因此可以做优化,可以单独打包生成文件,只在第一次打包的时候分析其中的代码,之后再打包的时候,直接用上一次的结果即可。

创建webpack.dll.js,将第三方模块单独打包到vendor.dll.js中。

// webpack.dll.js

const path = require('path')
module.export = {
    mode: 'production',
    entry: {
        vendors: ['react', 'react-dom', 'lodash'] // 把这三块内容做打包到dll文件夹下
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../dll'),
        library: '[name]' // 打包生成的库,在使用时全局保留的名字。此处为名字为vendors。
    }
}

在浏览器控制台输入vendors,则会打印响应的方法。

// package.json

{
    "scripts": {
        "build:dll": "webpack --config ./build/webpack.dll.js",
    }
}
npm run build:dll

单独配置了第三方模块打包的webpack。

我们还需要把生成的vendor.dll.js文件引入到index.html文件中。我们需要修改一下webpack.common.js的配置。

// 先安装
npm install add-asset-html-wepack-plugin --save-dev // 作用:往html-wepack-plugin 增加一些静态资源
// webpack.common.js

const AddAssetHtmlWebpackPlugin = require('add-asset-html-wepack-plugin')

plugins: [
  new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, ../dll/vendors.dll.js)
  })
]

上述配置将vendors.dll.js,引入到了index.html中。

目标:第三方模块只打包一次。此时该如何使用呢?此时并没有使用vendors.dll.js,而是依旧使用的node_module下的第三方模块。

目标:

  1. 第三方模块只打包一次。
  2. 引入第三方模块的时候,使用vendors.dll.js文件。

配置修改如下:

// webpack.dll.js

const path = require('path')
const webpack = require('webpack')

module.export = {
    mode: 'production',
    entry: {
        vendors: ['react', 'react-dom', 'lodash'] // 把这三块内容做打包到dll文件夹下
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../dll'),
        library: '[name]' // 打包生成的库,在使用时全局保留的名字。此处为名字为vendors。
    },
    plugin: [
      new webpack.DllPlugin({
          name: '[name]',  // 对这个库文件进行分析,分析的文件名,就是上面生成的文件名
          path: path.resolve(__dirname, '../dll/[name].manifest.json'), // 将第三模块的映射关系放到了名字叫做库名字的manifest.json文件下
      })
    ]
}

重新运行npm run prod:dll命令,就会生成vendors.manifest.json文件。之后,在进行webpack打包的时候,就会根据vendors.manifest.json的映射文件,和vendors的全局变量,对我们的源代码进行分析,一旦分析到有lodash、react、react-dom等库是在vendors.dll.js文件中时,就是使用vendors.dll.js中的内容,就不会在node_modules中引入我们的模块了。

如何结合vendors全局变量和vendors.manifest.json文件进行wepack的配置呢?做法如下

// webpack.common.js

const AddAssetHtmlWebpackPlugin = require('add-asset-html-wepack-plugin')

plugins: [
  new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, ../dll/vendors.dll.js)
  }),
  new webpack.DllReferencePlugin({ // dll引入的一个插件
      manifest: path.resolve(__dirname, '../dll/vendors.mainfest.json') // 当打包的时候引入第三方模块的时候,这个插件会在vendors.mainfest.json中找映射关系,找到映射关系就没必要再打包进来了,就会到全局变量里去拿。不在映射关系里时,才会在node_modules中进行查找。
  })
]

结合DllPluginDllReferencePlugin能够很大程度的提升代码打包效率。

我们还可以对dll文件进行拆分,如下:

// webpack.dll.js

module.export = {
    mode: 'production',
    entry: {
        vendors: ['lodash'],
        react: ['react', 'react-dom', ], // 将react拆分出来
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../dll'),
        library: '[name]' 
    },
    plugin: [
      new webpack.DllPlugin({
          name: '[name]',
          path: path.resolve(__dirname, '../dll/[name].manifest.json'), 
      })
    ]
}

生成文件如下:

react.dll.js
react.manifest.json
vendors.dll.js
vendors.manifest.json

在使用的地方也需要修改,即webpack.common.js修改

// webpack.common.js
配置两份

const AddAssetHtmlWebpackPlugin = require('add-asset-html-wepack-plugin')

plugins: [
  new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
  }),
  new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/react.dll.js')
  }),
  new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/vendors.mainfest.json')
  }),
  new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/react.mainfest.json')
  }),
]

多个dll文件时的配置,思路:plugins为数组。

// 先拆出两个基础plugin
const plugins = [
    new HtmlWebpackPlugin({
        template: 'src/index.html',
    }),
    new CleanWebpackPlugin(['dist'], {
        root: path.resolve(__dirname, '../')
    })
]

然后用node动态分析dll文件夹中有几个jsjson文件。

const fs = require('fs')

const plugins = [
    new HtmlWebpackPlugin({
        template: 'src/index.html',
    }),
    new CleanWebpackPlugin(['dist'], {
        root: path.resolve(__dirname, '../')
    })
]

const files = fs.readdirSync(path.resolve(__dirname, '../dll') // 读取dll文件夹的所有内容
console.log(files) // 打印查看files的内容

files.forEach(file => {
    if (/.*\.dll.js/.test(file)) {
        plugins.push(new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll', file) // 改为file
        }))
    }
    if (/.*\.manifest.json/.test(file)) {
        plugins.push(new webpack.DllReferencePlugin({
          manifest: path.resolve(__dirname, '../dll/', file)  // 改为file
       }))
    }
})

之后,webpack.common.jsplugins就可以做如下修改

// webpack.common.js
plugins: plugins,
因为键和值的名字一样,也可以写成
plugins,

之后,再改动webpack.dll.js的时候,就不用再改动webpack.common.js了。

总结: webpack可以通过DllPlugin的方式来提高打包速度。原因:每次打包的时候,第三方模块基本上是不变的,每次打包都要对模块进行分析,消耗性能。在第一次打包的时候,把第三方模块的文件单独打包,放到dll文件夹下管理,之后直接在dll文件夹里去引入之前打包好的第三方模块。

webpack.dll.js中,通过webpack.DllPlugin来生成xxx.mainfest.json关系映射文件。然后在webpack.common.js文件中,使用webpack.DllReferencePlugin插件,把对应的映射文件引入进来。这样,在源代码里引入第三方模块的时候,就会到dll目录下找,如果发现已经打包过,就直接用了,就能节约打包时间。最后,我们还要将dll.js文件挂载到index.html上,挂载通过AddAssetHtmlWebpackPlugin插件来实现。

使用DllPlugin提高打包速度,这些修改对于业务来说价值非常大

  1. 控制包文件大小
  2. thread-loaderparallel-webpack,happypack多进程打包。借助node的多进程提升打包速度。parallel-webpack用于多页打包,开启多个进程一起打包。
  3. 合理使用SourceMap,不同环境什么样的SourceMap是最合适的。
  4. 结合stats分析打包结果,借助打包分析工具,查看打包情况。
  5. 开发环境内存编译。开发时,代码放到内存中,内存的读取比硬盘读取快的多。
  6. 开发环境无用插件剔除。mode: 'development'development不需要做代码压缩,没有意义。

8. 多页面打包配置

单页面:整个应用中只有一个html文件.

// webpack.common.js

module.exports = {
    entry: {
        main: './src/index.js',
        list: './src/list.js',
    }
}

这样就打包生成了main.jslist.js两个文件,我们希望两个html分别引入两个html文件,而不是都引入到同一个html文件中。需要chunks参数来解决

接下来就来修改HtmlWebpackPlugin,帮我们生成html的插件.

参考文档: github.com/jantimon/ht…

new HtmlWebpackPlugin({
    template: 'src/index.html', // 以src下的index.html作为模板生成html
    filename: 'index.html', 
    chunks:   ['runtime', 'vendors', name]// 此html要引入的js文件。如:runtime运行时的代码
})

这样就可以实现在list.html中引入list.js文件,而不引入main.js.

实践配置
chunks: [name, 'vendor', 'antd', `runtime~${name}`],

多页面无非就是配置了多个HtmlWebpackPlugin

const makePlugins = (configs) => {
    const plugins = [
        new CleanWebpackPlugin(['dist'], {
            root: path.resolve(__dirname, '../')
        })
    ]
    
    Object.keys(configs.entry).forEach(item => {
        Plugins.push(
          new HtmlWebpackPlugin({
            template: 'src/index.html',
            filename: `${item}.html`, 
            chunks:   ['runtime', 'vendors', item]
          })
        )
    
    })
    
    // 将dll的配置文件也加入到plugin中
    
    const files = fs.readdirSync(path.resolve(__dirname, '../dll') // 读取dll文件夹的所有内容
    console.log(files) // 打印查看files的内容

    files.forEach(file => {
        if (/.*\.dll.js/.test(file)) {
            plugins.push(new AddAssetHtmlWebpackPlugin({
                filepath: path.resolve(__dirname, '../dll', file) // 改为file
            }))
        }
        if (/.*\.manifest.json/.test(file)) {
            plugins.push(new webpack.DllReferencePlugin({
              manifest: path.resolve(__dirname, '../dll/', file)  // 改为file
           }))
        }
   })
}

configs.plugins = makePlugins(configs)

这样就能最大程度的自动化的进行多页面打包了。