dllPlugin 动态连接库
本节学习一下webpack中比较重要的一个概念,叫webpack的动态链接库。
- 什么是动态链接库呢?
- 使用动态链接库能解决什么问题?
- 怎么去使用动态链接库?
前置工作
安装一下react和react-dom
yarn add react react-dom
修改index.js
// webpack-optimize\src\index.js
import React from 'react'
import { render } from 'react-dom'
// 将 h1标签 渲染到 root 节点下
render(<h1>jsx</h1>, window.root)
修改index.html
<!-- webpack-optimize\public\index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
修改配置文件
// webpack-optimize\webpack.config.js
let path = require('path')
let Webpack = require('webpack')
// 插件 用来复制html入口模板,到打包后的目录,并把打包后的js文件,以script标签的形式塞到模板文件里
let HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development', // 模式,默认两种模式 production 和 development
entry: './src/index.js', // 入口文件
devServer: { // 开发服务器的配置
port: 3000, // 默认端口是8080,这里可以改
progress: true, // 打包时候的进度条
contentBase: './build', // 以哪个文件夹作为服务的根目录 ,如果有就直接使用,没有的话就使用内存中的
open: true, // 服务启动完毕后,直接打开浏览器
compress: true, // 启动gzip压缩
},
// 打包后的输出路径
output: {
filename: 'bundle.js', // 打包后的文件名
path: path.resolve(__dirname, 'dist'), // 打包后输出的路径
},
// 配置一些loader
module: {
// 打包优化,不去继续解析内部是否还有别的依赖
noParse: /jquery/, // 如果发现是引用的jquery,就不再继续解析
rules: [
{
test: /\.js$/,
// 在检索js文件时,排除node_modules目录,默认node_modules也会检索到,所以可以排除一下
exclude: /node_modules/,
// 或者指定检索目录
include: path.resolve('src'),
use: {
loader: 'babel-loader',
// 配置一些它的选项
options: {
// 配置预设,有多个
presets: [
'@babel/preset-env', // 用来解析es6
'@babel/preset-react', // 用来解析react语法,jsx
]
}
}
},
]
},
// 注册插件
plugins: [
// 如果从moment中引用了 ./local 那么就忽略掉
new Webpack.IgnorePlugin(/\.\/local/, /moment/),
new HtmlWebpackPlugin({
template: './public/index.html', // 模板的位置
}),
]
}
启动服务, npm run dev,成功渲染。
优化的想法
不过这个时候打包出来的文件特别大,虽然就一行代码。这是因为每次都需要先打包一遍 react和react-dom
能不能先把react和react-dom先抽离出去,这两个文件属于第三方库,并不会更改,不用重新打包。
所以可以先把react和react-dom先独立打包一遍,然后再打包项目时,只需要重打包好的第三方库中引用过来。
先打包react和react-dom
测试
先打包一遍,然后开发时,引用我们打包好的文件。这样就不会重新打包了。相当于一个优化。
新建配置文件 webpack.config.react.js
// webpack-optimize\webpack.config.react.js
let path = require('path')
module.exports = {
mode: 'development',
entry: {
test: './src/test.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
新建测试文件 test.js
// webpack-optimize\src\test.js
// 如果直接打包这个文件,这个模块导出的结果是无法拿到的
module.exports = 'cheny hhh'
先来分析一下打包后的结果,看如何能拿到这个结果
打包,看下结果,npx webpack --config webpack.config.react.js
在 webpack打包出的文件解析 这章节中,我们研究过这个代码,再来看一下,删除没有用的代码
(function (modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/test.js");
})
({
"./src/test.js":
(function (module, exports) {
eval("// webpack-optimize\\src\\test.js\r\n// 如果直接打包这个文件,这个模块导出的结果是无法拿到的\r\nmodule.exports = 'cheny hhh'\n\n//# sourceURL=webpack:///./src/test.js?");
})
});
默认导出的结果,会被赋值到module.exports上,但是最后返回的结果,并没有被外部函数所接受,
其实我们可以手动的接收一下,比如
var a = (function (modules){})()
console.log(a)
// 这个 a 就是 返回的结果
我们不能每次都手动改,所以可以在配置文件中配置一下,让最后的结果,使用一个变量接收一下。
添加变量接收打包后的结果
修改配置文件
// webpack-optimize\webpack.config.react.js
let path = require('path')
module.exports = {
mode: 'development',
entry: {
test: './src/test.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
library: 'ab', // 打包最外部使用变量接收,可以用作动态链接库
libraryTarget: 'var', // 暴露给外部的方式,默认是var,还可以是 commonjs、var、this ...
}
}
然后再打包一下,看下结果,发现已经接收到了 npx webpack --config webpack.config.react.js
动态链接库,先打包第三方插件,然后开发时,引用打包好的结果
先打包react和react-dom,生成打包文件
修改配置文件
// webpack-optimize\webpack.config.react.js
let path = require('path')
let webpack = require('webpack')
module.exports = {
mode: 'development',
entry: {
react: ['react', 'react-dom'], //会直接打包node_modules 中的react和react-dom
},
output: {
filename: '_dll_[name].js', // 产生的文件名
path: path.resolve(__dirname, 'dist'),
library: '_dll_[name]', // 打包最外部使用变量接收,可以用作动态链接库 _dll_react
// libraryTarget: 'var', // 暴露给外部的方式,默认是var,还可以是 commonjs、var、this ...
},
// 动态链接库需要有一个清单文件,可以找到打包好的react和react-dom
// 使用webpack内置插件 dll
plugins: [
new webpack.DllPlugin({
name: '_dll_[name]', // name === library 好找到对应关系
path: path.resolve(__dirname, 'dist', 'manifest.json'), // 产生清单文件的目录,通过这个清单,可以找到已经打包的react和react-dom
})
],
}
打包,npx webpack --config webpack.config.react.js
打包成功,这样就先把react和react-dom打包好了
生成的manifest.json文件实际就是一个任务清单,与打包好的 _dll_react.js文件有一一映射的关系,通过manifest.json文件,就知道应该加载哪些模块代码。
修改index.html
这时候就需要在代码里手动引用已经打包好的react和react-dom
<!-- webpack-optimize\public\index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 引用打包好的react和react-dom 第三方库-->
<script src="/_dll_react.js"></script>
</body>
</html>
虽然手动的写好了这个文件,但是在开发的时候,webpack默认情况下并不知道怎么找这些模块。
所以在开发的配置文件中,就得告诉webpack怎么引用这些已经打包好的第三方模块
告诉webpack,代码中的 import React 让它去任务清单中找
我们在index.js中引用的react和react-dom
// webpack-optimize\src\index.js
// 这样写还是会直接打包,所以需要再配置文件告诉webpack
// 先从已经打包好的文件,dll动态链接库中加载,如果找不到了,再重新打包
import React from 'react'
import { render } from 'react-dom'
// 将 h1标签 渲染到 root 节点下
render(<h1>jsx</h1>, window.root)
修改配置文件
// webpack-optimize\webpack.config.js
let path = require('path')
let Webpack = require('webpack')
// 插件 用来复制html入口模板,到打包后的目录,并把打包后的js文件,以script标签的形式塞到模板文件里
let HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development', // 模式,默认两种模式 production 和 development
entry: './src/index.js', // 入口文件
devServer: { // 开发服务器的配置
port: 3000, // 默认端口是8080,这里可以改
progress: true, // 打包时候的进度条
contentBase: './dist', // 以哪个文件夹作为服务的根目录 ,如果有就直接使用,没有的话就使用内存中的
open: true, // 服务启动完毕后,直接打开浏览器
compress: true, // 启动gzip压缩
},
// 打包后的输出路径
output: {
filename: 'bundle.js', // 打包后的文件名
path: path.resolve(__dirname, 'dist'), // 打包后输出的路径
},
// 配置一些loader
module: {
// 打包优化,不去继续解析内部是否还有别的依赖
noParse: /jquery/, // 如果发现是引用的jquery,就不再继续解析
rules: [
{
test: /\.js$/,
// 在检索js文件时,排除node_modules目录,默认node_modules也会检索到,所以可以排除一下
exclude: /node_modules/,
// 或者指定检索目录
include: path.resolve('src'),
use: {
loader: 'babel-loader',
// 配置一些它的选项
options: {
// 配置预设,有多个
presets: [
'@babel/preset-env', // 用来解析es6
'@babel/preset-react', // 用来解析react语法,jsx
]
}
}
},
]
},
// 注册插件
plugins: [
// 引用动态链接库
new Webpack.DllReferencePlugin({
// 引用manifest,然后通过manifest找到暴露出的变量,通过变量再加已经打包好的模块
manifest: path.resolve(__dirname, 'dist', 'manifest.json')
// 这样的话就会先去查找清单,如果清单里找不到的时候,再去真正的打包react和react-dom
// 在html中必须手动引用一下那个文件,要不就找不到暴露的变量
}),
// 如果从moment中引用了 ./local 那么就忽略掉
new Webpack.IgnorePlugin(/\.\/local/, /moment/),
new HtmlWebpackPlugin({
template: './public/index.html', // 模板的位置
}),
]
}
重新打包一下,就发现明显的体积变小了。
总结
先把打包后的文件存好,手动把第三方库先打包一下,再使用的时候,就直接从这个打包好的文件中获取。
js// webpack-optimize\webpack.config.react.js let path = require('path') let webpack = require('webpack') module.exports = { mode: 'development', entry: { react: ['react', 'react-dom'], //会直接打包node_modules 中的react和react-dom }, output: { filename: '_dll_[name].js', // 产生的文件名 path: path.resolve(__dirname, 'dist'), library: '_dll_[name]', // 打包最外部使用变量接收,可以用作动态链接库 _dll_react // libraryTarget: 'var', // 暴露给外部的方式,默认是var,还可以是 commonjs、var、this ... }, // 动态链接库需要有一个清单文件,可以找到打包好的react和react-dom // 使用webpack内置插件 dll plugins: [ new webpack.DllPlugin({ name: '_dll_[name]', // name === library 好找到对应关系 path: path.resolve(__dirname, 'dist', 'manifest.json'), // 产生清单文件的目录,通过这个清单,可以找到已经打包的react和react-dom }) ], }
为了能找到暴露出来的变量,需要手动的引用一下这个打包好的文件
html<body> <div id="root"></div> <!-- 引用打包好的react和react-dom 第三方库--> <script src="/_dll_react.js"></script> </body>
为了告诉webpack,再碰到引用动态链接库中的第三方插件时,就优先重动态链接库中获取,如果没有再重新打包
js// webpack-optimize\webpack.config.js let path = require('path') let Webpack = require('webpack') // 插件 用来复制html入口模板,到打包后的目录,并把打包后的js文件,以script标签的形式塞到模板文件里 let HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', // 模式,默认两种模式 production 和 development entry: './src/index.js', // 入口文件 devServer: { // 开发服务器的配置 port: 3000, // 默认端口是8080,这里可以改 progress: true, // 打包时候的进度条 contentBase: './dist', // 以哪个文件夹作为服务的根目录 ,如果有就直接使用,没有的话就使用内存中的 open: true, // 服务启动完毕后,直接打开浏览器 compress: true, // 启动gzip压缩 }, // 打包后的输出路径 output: { filename: 'bundle.js', // 打包后的文件名 path: path.resolve(__dirname, 'dist'), // 打包后输出的路径 }, // 配置一些loader module: { // 打包优化,不去继续解析内部是否还有别的依赖 noParse: /jquery/, // 如果发现是引用的jquery,就不再继续解析 rules: [ { test: /\.js$/, // 在检索js文件时,排除node_modules目录,默认node_modules也会检索到,所以可以排除一下 exclude: /node_modules/, // 或者指定检索目录 include: path.resolve('src'), use: { loader: 'babel-loader', // 配置一些它的选项 options: { // 配置预设,有多个 presets: [ '@babel/preset-env', // 用来解析es6 '@babel/preset-react', // 用来解析react语法,jsx ] } } }, ] }, // 注册插件 plugins: [ // 引用动态链接库 new Webpack.DllReferencePlugin({ // 引用manifest,然后通过manifest找到暴露出的变量,通过变量再加已经打包好的模块 manifest: path.resolve(__dirname, 'dist', 'manifest.json') // 这样的话就会先去查找清单,如果清单里找不到的时候,再去真正的打包react和react-dom // 在html中必须手动引用一下那个文件,要不就找不到暴露的变量 }), ] }
动态链接库的核心就是
- 先打包第三方库,通过manifest清单,建立对应关系。
- webpack自己实现的require方法没有把闭包最后的返回值暴露出来,所以需要先暴露出来,通过output->library字段来声明,这时默认会在外部使用一个var的变量接收
- 在index.html中引用这个打包好的文件,为了能找到这个全局变量。
- 真正开发的时候,告诉webpack,先从动态链接库中查找,没有再重新打包,查找也是通过manifest来建立的链接关系。
参考
https://www.bilibili.com/video/BV1a4411e7Bz?p=22&spm_id_from=pageDriver