1. Library打包
1.1 对库文件进行打包
库文件的打包:我们新建一个工程,新建index.js math.js string.js
,代码如下:
index.js
文件
import * as math from './math.js'
import * as string from './string.js'
export default { math, string }
math.js
文件:
export function add (a, b) {
return a + b;
}
export function minius (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
}
然后我们打包输出,我们这个库,在给被人使用的是,别人会有很多种方式,进行使用;比如:
ES module
:
import library from ‘library’
commonJS
方式:
const library = require('library')
AMD
引入方式:
require(['library'], function () {
})
我们如果想让我们的库文件,在外面可以用上面的这些方式引入,我们可以进行配置,在webpack.config.js
中配置如下:libraryTarget: 'umd'
代码意思就是不管是在AMD
的环境,还是CommonJS
的环境、或者是import
方式引入,都可以正确使用我们的库文件。
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
libraryTarget: 'umd'
}
}
如果我们想用
<script src="library.js"></script>
这种方式引入,然后使用library.math
这种全局变量来使用我们的库,这时候我们需要在webpack.config.js
中添加一个配置项:library: 'library',
意思是将library
挂载到了全局变量上,这样,就可以通过script
引入,然后进行使用了。
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library',
libraryTarget: 'umd'
}
其实libraryTarget
跟library
相互作用的,如果配置成上面的,是没有多大关系的,我们可以修改 libraryTarget: 'this'
意思就是它不支持AMD commonJS
等这些语法引入了,但是我们的library
会挂载到全局的this
上面。同样,也可以是window
;,如果在nodeJS
环境下,也可以配置 global
;
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library',
libraryTarget: 'this'
}
我们有时候在我们的库文件中,我们使用了第三方的库文件,比如我们将我们的string.js
代码改成下面的:我们引入了lodash
,
import _ from 'lodash'
export function join (a, b) {
return _.join([a, b],' ')
}
然后别人在使用我们的库文件的时候,有可能也会使用lodash
这个库文件,这样可能会导致一个问题,用户打包后的代码会出现两个lodash
文件,我们可以通过配置,来去除这种重复引用;externals: ['lodash'],
意思就是打包过程中,如果遇到lodash
就忽略这个库文件,然后比尔使用我们这个库的使用还是需要在自己的业务代码中引入lodash
库。
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
externals: ['lodash'],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library',
libraryTarget: 'umd'
}
}
这里的externals
也可以是一个对象:下面配置的意思是如果在commonJS
环境下,我们使用library
库文件:
const library = require('library')
这样,我们引入lodash
的时候,命名必须定义成lodash
:如下:const lodash = require('lodash')
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
externals: {
lodash: {
commonjs: 'lodash',
}
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library',
libraryTarget: 'umd'
}
}
1.2 发布一个包
首先我们需要修改pachage.json
中入口改成:./dist/library.js
表示,我们的库文件最终给别人使用的时候,入口文件是./dist/library.js
文件。然后在npm
上注册一个账号;
然后在命令行里面输入npm adduser
,然后输入用户名与密邮箱等。然后输入命令npm publish
进行发布我们的包文件,这里需要注意的是,包的名字,不能重复;需要修改name
,值,然后我们使用我们发布的包,可以运行命令npm install 包名
就可以了。
2. PWA(Progressive Web Application)的打包配置
这个是一个新的前端技术。首先一个知识点就是在配置js
文件打包输出的时候,配置this
的指向:在webpack.config.js
中的配置打包输出js
的loader
中,进行配置:loader: "imports-loader?this=>window"
这句话就是将this
指向了window
对象。
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
},
{
loader: "imports-loader?this=>window"
}
我们可以通过一个http-server
模块来模拟我们的服务器,当然也可以使用live-server
两个都是一样的。输入命令npm install http-server -D
进行安装。然后我们在package.json
里面进行配置一个命令,来运行我们的服务器:"start": "http-server dist"
代码的意思就是在我们打包输出的dist
的文件夹中启动一个服务器。
"scripts": {
"start": "http-server dist"
},
输入运行命令之后,我们可以看到我们的网页正常显示了。如果这个时候,我们将服务器断开,浏览器会出现:无法访问此网站,也就是我们的网站挂了,而pwa
技术是如果你第一次访问网站,访问成功之后,突然之间服务器挂了,你第二次再次访问这个网站的时候,他会利用你本地的缓存,可以利用缓存,把之前访问的页面展示出来。也就是说,即便服务器挂了,我在本地还是可以看到之前访问到的页面。
我们可以在webpack
中,通过一个插件来实现:
输入命令:npm install workbox-webpack-plugin --save-dev
进行安装,我们只需要在线上环境之后,进行使用pwa
技术,让用户体验更好,所以,我们只需要修改webapck.prod.js
的配置文件。
.....
const WorkboxPlugin = require('workbox-webpack-plugin')
const prodConfig = {
....
plugins: [
....
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true
})
....
],
}
....
然后我们进行运行npm run build
进行打包,现在你可以看到,生成了两个额外的文件:service-worker.js
和名称冗长的 precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js
。service-worker.js
是Service Worker
文件,precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js
是 service-worker.js
引用的文件,所以它也可以运行。可能你本地生成的文件可能会有所不同;但是应该会有一个service-worker.js
文件。但是我们重复上面的操作之后,关闭服务器,再次访问的时候,还是不能查看我们上次访问的网页,因为我们还差一步,那就是注册Service Worker
,在我们的src
目录中的主入口js
文件加上下面代码:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
再次运行npm build build
来构建包含注册代码版本的应用程序。然后用 npm start
将构建结果serve
到服务下。导航至 http://localhost:8080
并查看 console
控制台。应该看到:SW registered
,现在来进行测试。停止 server
并刷新页面。如果浏览器能够支持 Service Worker
,应该可以看到你的应用程序还在正常运行。
3. TypeScript 的打包配置
TypeScript
可以进行代码的规范,也可以很方便的进行报错提示;可以有效的提升JavaSCript
的可维护性。首先我们进行配置打包typescript
的规则,我们要使用一个ts-loader
这个loader
来进行打包tsx
的文件,当然我们首先需要安装这个loader
,使用这个loader
的时候,你必须要安装typescript
,所以运行下面的命令进行安装:npm install ts-loader typescript --save-dev
配置webpack.config.json
如下:
const path = require('path')
module.exports = {
entry: './src/index.tsx',
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
但是这样还是不能够进行打包,我们需要添加一个tsconfig.json
,进行配置打包TypeScript
的规则:
{
"compilerOptions": {
"outDir": "./dist",//打包输出的地址
"module": "es6",// 模块的引用方式是使用esmodule的语法
"target": "es5",//打包成es5的代码
"allowJs": true//是否允许在TypeScript中引入js模块
}
}
这样就可以打包成功了;我们之所以使用TypeScript
是进行规范我们代码书写,以及类型引用的正确;比如我们使用lodash
这样的库文件,我们在使用这个库的时候,我们有时候不知道这个函数的参数是什么 ,也没有提示我们书写的正确性,只有运行之后,才会知道;当然你也可以去查看文档。我们可以使用对应库文件的TypeScript
类型文件,比如我们前面说lodash
,我们进行输入命令npm install @types/lodash --save-dev
进行安装对应的TypeScript
类型文件,然后我们调用里面的函数,如果传入的参数不对,会直接进行提示;
我们在TypeScript
中引用lodash
,需要使用下面的代码:
import * as _ from 'lodash'
如果需要使用其他库文件,就安装对应的TypeScript
类型文件。如果你不知道有还是没有,可以在这个网站;https://microsoft.github.io/TypeSearch/
进行搜索对应的库文件。
4. 使用 WebpackDevServer 实现请求转发
我们一般在前端发送ajax
请求是使用axios
这个库进行发送请求,首先我们需要进行安装;一个简单的请求如下:
import axios from 'axios'
axios.get('http://www.dell-lee.com/react/api/header.json')
.then((res) => {
console.log(res)
})
我们一般在生产环境的时候,是使用其他服务器的api
接口,并不是使用真正的后台接口,是一台测试的服务器,线上环境我们是使用真正的接口数据。所所以这个时候,我们就需要进行对接口的转发。配置devServer
如下:'/react/api': 'http://www.dell-lee.com'
代码的意思就是当我发送请求/react/api
地址的时候,接口转发到http://www.dell-lee.com
这个地址。进行获取对应的数据。
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/react/api': 'http://www.dell-lee.com',
},
hot: true,
},
然后在发送请求的地址如下代码:
import axios from 'axios'
axios.get('/react/api/header.json')
.then((res) => {
console.log(res)
})
也可以对一个接口请求的地址进行转发,比如我们现在请求的是http://www.dell-lee.com/react/api/header.json
实际我们需要请求的是http://www.dell-lee.com/react/api/demo.json
,可以在devserver
配置如下;
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/react/api': {
target: 'https://www.dell-lee.com',
pathRewrite: {
'header.json': 'demo.json'
}
},
},
hot: true,
},
当然我们只有在开发环境的时候进行上面的配置是有用的,有时候我们请求的地址是https
协议的网址,需要加一个配置项:secure: false,
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/react/api': {
target: 'https://www.dell-lee.com',
secure: false,
pathRewrite: {
'header.json': 'demo.json'
}
},
},
hot: true,
},
我们在这里也可以进行拦截,比如下面的代码配置:bypass
里面的配置项就是说当发送请求要接手的是html
页面数据的时候,也就是说请求是一个html
的地址的时候,直接跳转到index.html
页面。
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/react/api': {
target: 'https://www.dell-lee.com',
secure: false,
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
},
pathRewrite: {
'header.json': 'demo.json'
}
},
},
hot: true,
},
如果我们代理多个路基,可以进行下面这样的配置:我们上面只有一个'/react/api'
一个路径,下面代码表示遇到'/auth', '/api'
这两个地址,都是转发到https://www.dell-lee.com
这个服务器。
devServer: {
proxy: {
context: ['/auth', '/api'],
target: 'https://www.dell-lee.com',
},
hot: true,
},
如果我们想做一个根目录的路径的转发,也就是'/'
,我们需要将配置项的index
设置为false
或者为''
空字符串,如下配置:
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
index: '',
'/': {
target: 'https://www.dell-lee.com',
secure: false,
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
},
pathRewrite: {
'header.json': 'demo.json'
}
},
},
hot: true,
},
我们可以设置changeOrigin: true
来覆盖一些请求服务器对origin
进行的配置,如下代码:
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
index: '',
'/': {
target: 'https://www.dell-lee.com',
secure: false,
changeOrigin: true,
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
},
pathRewrite: {
'header.json': 'demo.json'
}
},
},
hot: true,
},
5. WebpackDevServer 解决单页面应用路由问题
如果我们需要实现react
的路由功能实现单页面应用,需要安装路由组件,输入命令:npm intall react-router-dom --save
,然后我们写了两个组件home
跟 list
两个组件,代码如下:
import React, { Component } from 'react'
import {BrowserRouter, Router} from 'react-router-dom';
import ReactDom from 'react-dom'
import Home from './home.js';
import List from './list.js'
class App extends Component {
return () {
return (
<BrowserRouter>
<div>
<Router path='/' component={Home} />
<Router path='/list' component={List}/>
</div>
</BrowserRouter>
)
}
}
ReactDom.render(<h1>Hello, world!</h1>, document.getElementById('root'))
home.js
代码:
import React, { Component } from 'react';
class Home extends Component {
render () {
return <div>HomePages</div>
}
}
export default Home
list.js
代码:
import React, { Component } from 'react';
class Home extends Component {
render () {
return <div>HomePages</div>
}
}
export default Home
我们写完代码之后,运行;当我们地址中访问list
页面的时候,会发现找不到对应的页面,这是因为,webpack
将我们页面 的请求以为是发送到后台的api
接口。解决这个问题,我们就需要在devserver
中进行配置historyApiFallback
的配置项,具体配置如下:historyApiFallback: true,
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
historyApiFallback: true,
proxy: {
'/react/api': {
target: 'https://www.dell-lee.com',
secure: false,
changeOrigin: true,
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
},
pathRewrite: {
'header.json': 'demo.json'
}
},
},
hot: true,
},
这个选项也可以是一个对象,进行配置规则,进行进一步配置:代码意思是,当访问item.html
的时候,实际访问的是list.html
页面。
historyApiFallback: {
rewrites: [
{
from: '/item.html',
to: '/list.html'
}
]
},
其实我们前面配置的historyApiFallback: true,
实际等价于下面的配置:也就是不官方文哪一个都是指向index.html
页面。
historyApiFallback: {
rewrites: [
{
from: '/\.*/',
to: '/index.html'
}
]
},
6. EsLint 在 Webpack 中的配置
EsLint
就是规范我们代码书写的插件,输入命令npm install eslint --save-dev
,然后在我们的项目中,通过命令npx eslint --init
快速生成我们代码约束的配置文件,按照提示的信息进行配置我们的代码约束规则。我们可以输入命令npx eslint src
这样的命令。来检测src
目录下文件的书写规范,或者直接可以检测一个文件npx eslint index.js
这样进行检测。
如果我们配置的代码检测的规范是使用airbnb
这种规范,我们会发现,我们去检测我们写的react
的代码的时候,会提示很多错误,这是因为这个规范只是检测常规的js
代码的一些规范,不会进行解析react
的代码,进行检测规范,这个时候,我们需要添加一个解析器,.eslintrc.js
配置代码如下:需要安装babel-eslint
这个解析器,输入命令npm install babel-eslint --save-dev
进行安装。
module.exports = {
"extends": "airbnb",
"parser": "babel-eslint"
}
也可以直接在vscode
这个编译器中进行安装eslint
插件,进行检测我们的代码规范,进行提示。我们也可以自己进行配置一些规范是否启用,在.eslintrc.js
中进行配置,比如我们不需要一个配置,直接在rules
中将对应的规范配置为0:
module.exports = {
"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
"react/prefer-stateless-function": 0
}
}
还有一些eslint
的配置可以查看另一篇文章:https://blog.csdn.net/cj9551/article/details/90740377#15_ESLint_457 里面有对应的eslint
的配置。
如果需要使用全局变量,可以在配置项中进行配置:document: false
不会被重新覆盖。
module.exports = {
"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
"react/prefer-stateless-function": 0
},
global: {
document: false
}
}
如果有的人的编译软件,是不能安装eslint
查看,我们可以直接在webpack
中进行配置这样一个检测环境,首先安装eslint-loader
这样一个loader
,输入命令npm install eslint-loader --save-dev
,然后在webpack.config.js
中进行配置:
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader']
}]
},
然后运行命令,就可以在终端窗口看到一些代码规范的提示,我们也可以在devserver
中进行添加一个overlay: true
的配置,将代码规范的提示,直接显示在浏览器的页面中:
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
// historyApiFallback: true,
historyApiFallback: {
rewrites: [
{
from: '/item.html',
to: '/list.html'
}
]
},
proxy: {
'/react/api': {
target: 'https://www.dell-lee.com',
secure: false,
changeOrigin: true,
overlay: true,
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
},
pathRewrite: {
'header.json': 'demo.json'
}
},
},
hot: true,
},
7. webpack 性能优化
- 跟上技术的迭代(node,npm,yarn):升级我们对应的工具;
webpack
运行在node
之上。升级到新的版本,也会间接提升webpack
打包速度。 - 在尽可能少的模块上应用
Loader
: 比如我们在打包js
的文件的时候使用babel-loader
进行转换,我们可以通过includes
以及excludes
等选项,进行配置不对其他库文件进行转换,降低使用loader
的频率。 Plugin
尽可能精简并确保可靠:比如我们在线上环境的配置中使用了MiniCssExtractPlugin
插件进行将css
代码进行压缩,但是我们在生产环境中,并不需要使用这个插件进行压缩我们的代码,要尽可能的精简。减少了代码压缩的时间。还有就是尽量使用webpack
官网中推荐的插件。- 一般我们在打包配置
js
的文件,与jsx
的文件,打包规则是一样的,我们在test
匹配的时候,可以使用test: /\.jsx?$/,
进行匹配打包。 - 尽量减少下面的一些操作:我们有时候通过
import
的方式进行引入一个模块,我们在模块的结尾并没有明确是加载哪个类型的文件,import List from './list'
默认是加载js
结尾的文件,但是有时候我们的库文件是jsx
结尾的文件,我们也想通过这种方式进行加载jsx
文件,可以进行下面的配置:extensions: ['.js', '.jsx']
代码意思就是引入包之后,他会按照是否有对应的js
,如果有就进行加载,如果没有继续找是否有对应的jsx
文件进行加载。
module.exports = {
.....
// 入口文件
entry: {
main: './src/index.js',
},
resolve: {
extensions: ['.js', '.jsx']
}
.....
}
这个选项还可以配置我们通过引入路径,然后对路径中的文件名进行对应的加载,比如:import List from './list/'
这个list
是src
目录中的一个文件夹名,然后该文件夹中有很多js
文件,如果按照这种引入方式,他会自动检查是否有文件名叫index
的文件,然后进行引入,如果没有,就不会进行引入,我们可以通过配置resolve
选项,配置加载的文件名:mainFiles: ['index', 'list']
代码意思是如果通过路径的方式进行引入模块,首先检查进行加载index
的文件名,如果没有,进行检查是否有list
的文件名进行引入。
module.exports = {
.....
// 入口文件
entry: {
main: './src/index.js',
},
resolve: {
extensions: ['.js', '.jsx'],
mainFiles: ['index', 'list']
}
.....
}
同样上面的extensions
,以及 mainFiles
的配置,会影响打包的性能。在resolve
中,我们也可以进行设置路径的别名:比如我们有时候想引入一个模块通过一个字符串,进行代表该库文件的地址,进行引入模块:import list from 'listpath'
这样的方法进行引入模块。我们就可以设置别名,让listpath
指向该模块的地址。
module.exports = {
.....
// 入口文件
entry: {
main: './src/index.js',
},
resolve: {
extensions: ['.js', '.jsx'],
mainFiles: ['index', 'list'],
alias: {
listpath: path.resolve(__dirname, '../src/list')
}
}
.....
}
8. 使用DllPlugin提高打包速度
我们实现的思路:
- 第三方模块打包一次
- 我们引入第三方模块的时候,要去使用打包输出的第三方模块的文件进行引入。
我们在打包自己的项目的时候会发现,如果引入了过多的第三方库文件,会使打包的速度降低;我们可以想如果能够实现在第一次打包的时候就去分析第三方模块的代码,然后再次打包的时候,就根据前面分析的结果进行打包,不用再次分析,提高打包效率;
添加一个webpack.dll.js
的配置文件,代码如下:这里是将我们引入的第三方模块打包到dll
文件夹中,library: '[name]'
这句代码的意思就是,将我们打包后的第三方模块通过变量的形式暴露到全局中;变量的名字叫vender
;
const path = require('path')
nodule.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom', 'lodash']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]'
}
}
然后我们需要安装一个插件,将我们的打包后的文件进行挂载到我们打包输出的页面中,输入命令npm install add-asset-html-webpack-plugin --save
这个插件的作用就是往html
页面中去增加静态资源。然后在webpack.config.js
中进行配置:这样页面就会将我们打包输出的第三方模块的vender.dll.js
进行挂载;
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
......
module.exports = {
......
plugins: [
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
})
]
.......
}
接下来我们就要实现引入第三方模块的时候,通过我们上一步打包输出的文件进行引入,首先我们需要进行分析前面打包输出的第三方库文件,将分析结果进行保存,使用DllPlugin
进行分析,在webpack.dll.js
的配置文件中进行如下配置:name: '[name]',
是指要分析的库名,path: path.resolve(__dirname, '../dll/[name].manifest.json')
代码意思就是将分析的结构放在../dll/[name].manifest.json
const path = require('path')
const webpack = require('webpack')
nodule.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom', 'lodash']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].manifest.json')
})
]
}
然后我们做的就是根据我们前面生成的library: '[name]'
全局变量的名字,以及分析的文件,在webpack
进行配置打包:
在webpack.config.js
中使用一个插件:manifest: path.resolve(__dirname, '../dll/vendors.manifest.js')
意思就是我们在代码中如果引入了第三方模块。他就会在vendors.manifest.json
这个文件中去找对应的第三方模块的映射关系,如果可以找到,他就会知道这个第三方模块不用再次引入了,直接使用vendors.dll.js
这个文件就可以了,他会在全局变量中进行使用我们的第三方模块。如果没有映射关系,就会进行将第三方库进行打包。
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
......
module.exports = {
......
plugins: [
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
})
]
.......
}
然后我们在package.json
里面配置一个命令:"build:dll": "webpack --config ./build/webpack.dll.js"
来执行我们的打包第三方模块的命令。然后进行打包,会发现比之前的打包速度要快很多。
其实我们前面打包第三方库输出额时候可以进行拆分:
const path = require('path')
nodule.exports = {
mode: 'production',
entry: {
vendors: ['lodash'],
react: ['react', 'react-dom']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]'
}
}
对应的在webpack.config.js
中也需要进行配置:
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
......
module.exports = {
......
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.manifest.json')
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll/react.manifest.json')
})
]
.......
}
如果我们按照上面的,配置很多的话,就特别繁琐,我们可以通过下面的方法进行简单化:
const plugins = [
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpakcPlugin({
template: './src/index.html'
})
]
const fs = require('fs')
const files = fs.readdirSync(path.resolve(__dirname, './dll'))
files.forEach(file => {
if(/.*\.dll.js/.test(file)) {
plugins.push(
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest.json/.test(file)) {
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
})
}
})
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
......
module.exports = {
......
plugins: plugins
.......
}
9. Webpack其他打包优化方法
- 控制包文件大小:在源代码中将用不到的包文件在打包的时候通过
Tree Shaking
进行去除,或者直接不进行引入;也可以通过splitchunkplugins
插件进行包的拆分,提高打包速率。 tread-loader, parallel-webpack, happypack
多进程打包。- 合理使用
sourceMap
:不同环境打包,使用最佳的sourceMap
方式。 - 结合
stats
分析打包结果:根据分析结果进行优化。 - 开发环境内存编译。
- 开发环境无用插件剔除:比如开发环境中不需要对代码进行压缩,将
mode
设置为production
。
10. 多页面打包配置
我们前面所说的都是单页面进行打包,也就是打包之后就生成一个index.html
页面。比如说我们有两个页面一个index.js
,一个list.js
内容分别如下:
index.js
:
class App extends Component {
return () {
return (
<div>
index
</div>
)
}
}
ReactDom.render(<h1>Hello, world!</h1>, document.getElementById('root'))
list.js
:
class App extends Component {
return () {
return (
<div>
list
</div>
)
}
}
ReactDom.render(<h1>list!</h1>, document.getElementById('root'))
这是两个不同的页面,我们希望打包之后有两个html
页面,一个引入iindex.js
打包之后的文件,一个引入list.js
打包之后的文件。首先我们需要修改打包的入口文件,将list.js
文件进行添加:
// 入口文件
entry: {
main: './src/index.js',
list: './src/list.js'
},
然后修改我们之前配置的打包输出设置的模板html
:设置打包输出的html
的文件名,并且设置页面引用的包文件。
plugins: [
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpakcPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['runtime', 'vendors', 'main']
}),
new HtmlWebpakcPlugin({
template: './src/index.html',
filename: 'list.html',
chunks: ['runtime', 'vendors', 'list']
})
]
我们如果有很多页面进行打包,如果重复上面的操作就特别麻烦,所以我们可以进行处理上面的代码:
// 引入node核心模块path
const path = require('path')
// 将我们写的html文件,进行打包;
const HtmlWebpakcPlugin = require('html-webpack-plugin')
// 清除上次打包生成的js文件
const CleanWebpackPlugin = require('clean-webpack-plugin')
const webpack = require('webpack');
const fs = require('fs')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
const makePlugins = (configs) => {
const plugins = [new CleanWebpackPlugin()]
const files = fs.readdirSync(path.resolve(__dirname, './dll'))
Object.keys(configs.entry).forEach(item => [
plugins.push(new HtmlWebpakcPlugin({
template: './src/index.html',
filename: `${item}.html`,
chunks: ['runtime', 'vendors', item]
}))
]);
files.forEach(file => {
if(/.*\.dll.js/.test(file)) {
plugins.push(
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest.json/.test(file)) {
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
})
}
})
return plugins;
}
const configs = {
// 配置打包模式
mode: 'development',
devtool: 'cheap-module-eval-source-map',
// 入口文件
entry: {
main: './src/index.js',
list: './src/list.js'
},
devServer: {
// 服务器启动的根路径
contentBase: './dist',
open: true,
proxy: {
'/api': 'http://localhost:3000'
},
hot: true,
hotOnly: true
},
stats: { children: false },
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 204800
}
}
}, {
test: /\.vue$/,
use: {
loader: 'vue-loader'
}
}, {
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: 'file-loader'
}
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},{
test: /\.(html)$/,
use: {
loader: 'html-loader',
}
}]
},
optimization: {
usedExports: true
},
// 打包出的文件配置
output: {
path: path.resolve(__dirname, 'dist')
}
}
module.exports = configs
configs.plugins = makePlugins(configs)