1. 开发环境的依赖
git托管代码
远程git仓库使用gitee.com
使用yarn来安装相关依赖的包,yarn@1.22.1
webpack@4.39.2
webpack-cli@3.3.7
webpack-dev-server@3.8.0
node.js@12.16.1
node-sass@4.13.0
2. 初始化项目并关联远程git仓库
- 在本地创建项目目录
进入目录,npm初始化项目
yarn init -y
- 初始化git仓库
如下命令
#初始化项目
git init
echo "/node_modules\n/build" >> .gitignore
#关联远程仓库
git remote add origin <url>
注1
:项目关联远程仓库时如下报错
fatal: not a git repository (or any of the parent directories): .git
如下图
提示说没有.git这样一个目录,说不是一个git仓库,原来是没有初始化git项目,使用git init初始化一下就可以了.
注2
:在完成项目关联远程仓库时,把项目推到远程仓库时报如下错误
error: src refspec master does not match any
如下图:
引起该错误的原因是,目录中没有文件,空目录是不能提交上去的。
原来我的本地项目没有Add 和commit,commit之后就可以把本地仓库的项目推到关联的远程仓库了
但如果是空目录想推项目打远程仓库的话,解决办法是
touch READMEgitadd README git commit -m '注释内容'git push origin master
3. webpack配置
3.1 基础配置设置
- 在项目根目录下创建文件夹
src
,里面创建文件index.js
作为webpack的入口文件
代码如下:
import React from 'react';
import reactDom from 'react-dom';
const App = () => (
<div>
test page
</div>
);
reactDom.render(<App/>, document.getElementById('root'));
注意
:创建js文件后,会显示格式错误,如下图
解决办法:
菜单栏 File=> settings => Languages & Frameworks => JavaScript => JavaScript language version
JavaScript 的版本设置为React JSX即可。
- 创建模板文件
/public/index.html
webpack打包后的文件将添加到该文件
代码如下
<!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>
<div id="root"></div>
</body>
</html>
- 创建
webpack
开发环境下配置文件/webpack/webpack.config.dev.js
代码如下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, '../src/index.js'),
output: {
path: path.resolve(__dirname, '../build'),
filename: 'js/[name].[hash].bundle.js',
},
module: {
rules: [
{
test: /\.(mjs|js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
}
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
})
],
resolve: {
extensions: ['.mjs', '.js', '.jsx'],
},
};
- 创建
webpack
生产环境下配置文件/webpack/webpack.config.prod.js
代码如下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, '../src/index.js'),
output: {
path: path.resolve(__dirname, '../build'),
filename: 'js/[name].[hash].bundle.js',
},
module: {
rules: [
{
test: /\.(mjs|js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
}
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
})
],
resolve: {
extensions: ['.mjs', '.js', '.jsx'],
},
};
3.2 安装基础插件包
1. webpack 相关依赖包、插件
-
webpack: webpack 基础包
-
webpack-cli: webpack cli 工具包
-
html-webpack-plugin: webpack 插件, 用于将打包后的文件添加到指定的 html 内,即能生成单独的html文件,还可以对应的脚本和样式都自动的插入到合适的位置,不用手动的引入,还支持哈希的功能,防止在更新的时候有浏览器缓存
-
webpack-dev-server: webpack 开发环境工具, 创建一个开发环境,为webpack项目提供web服务的工具,能让我们在浏览器里通过http的形式访问编译好的文件
-
babel-loader: weboack loader, 用于编译打包 js 文件
-
@babel/core: babel 依赖包, 将 js 代码分析成 ast
-
@babel/preset-react: webpack react 相关预设
-
@babel/preset-env: weboack react 相关预设, 这样就可以使用最新的 js 相关语法
yarn add webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader @babel/core @babel/preset-react @babel/preset-env -D
-
创建
babel
配置文件.babelrc
代码如下:
{
"presets": [
"@babel/preset-react",
"@babel/preset-env"
]
}
2. react 相关依赖包、包
- react
- react-dom
安装命令如下:
yarn add react react-dom -S
3. 修改package.json的script命令
- 修改
package.json
: 添加 npm 脚本,dev启动命令,build生产环境打包命令
内容如下:
"scripts": {
"dev": "webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
"build": "webpack --config ./webpack/webpack.config.prod.js"
}
3.3 html-webpack-plugin简单使用
1.template
使用,我们可以指定html模板
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
})
]
};
2.title
使用,我们可以指定模板title
在指定html模板内的代码如下
<title><%= htmlWebpackPlugin.options.title %></title>
webpack的配置文件的代码如下
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: '测试',
template: path.resolve(__dirname, '../public/index.html'),
})
]
};
3. **filename
**的使用,代码如下
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: '测试',
template: path.resolve(__dirname, '../public/index.html'),
filename: 'index.html' //打包后的文件名
})
]
};
4. html-webpack-plugin 配置文件 config 的灵活使用
有时候,我们的脚手架不仅仅给自己使用,也许还提供给其它业务使用,html
文件的可配置性可能很重要,比如:你公司有专门的部门提供M页的公共头部/公共尾部,埋点jssdk以及分享的jssdk等等,但是不是每个业务都需要这些内容。
一个功能可能对应多个 js
或者是 css
文件,如果每次都是业务自行修改 public/index.html
文件,也挺麻烦的。首先他们得搞清楚每个功能需要引入的文件,然后才能对 index.html
进行修改。
此时我们可以增加一个配置文件,业务通过设置 true
或 false
来选出自己需要的功能,我们再根据配置文件的内容,为每个业务生成相应的 html
文件。
首先,我们在 public
目录下新增一个 config.js
( 文件名你喜欢叫什么就叫什么 ),将其内容设置为:
//public/config.js 除了以下的配置之外,这里面还可以有许多其他配置,例如,pulicPath 的路径等等
module.exports = {
dev: {
template: {
title: '测试1',
header: false,
footer: false
}
},
build: {
template: {
title: '测试2',
header: true,
footer: false
}
}
}
现在,修改下 webpack.config.dev.js
和webpack.config.prod.js
:
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development';
const config = require('./public/config')[isDev ? 'dev' : 'build'];
modue.exports = {
//...
mode: isDev ? 'development' : 'production'
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', //打包后的文件名
config: config.template
})
]
}
相应的,我们需要修改下我们的 public/index.html
文件(js和css仅作为示意):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<% if(htmlWebpackPlugin.options.config.header) { %>
<link rel="stylesheet" type="text/css" href="//common/css/header.css">
<% } %>
<title><%= (htmlWebpackPlugin.options.config.title) %></title>
</head>
<body>
</body>
<% if(htmlWebpackPlugin.options.config.header) { %>
<script src="//common/header.min.js" type="text/javascript"></script>
<% } %>
</html>
注意
:process.env
中默认并没有 NODE_ENV
,这里配置下我们的 package.json
的 scripts
为了兼容Windows和Mac,我们先安装一下 cross-env
yarn add cross-env -D
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
"build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.prod.js",
}
}
注意
:在此,我们配置了开发环境的webpack配置文件,与生产环境的webpack配置文件
我们运行 yarn run dev
跟运行 yarn run build
,对比 build/index.html,可以看到 yarn run build
生成的文件index.html,文件中引入了对应css和js。并且对应title内容也不一样。
3.4 测试
1. 执行 yarn run dev
测试项目是否能够正常运行
2. 执行 yarn run build
测试是否能够正常对项目进行打包、编译, 编译后目录结构如下
.
├── index.html
└── js
└── main.0b16f9b82b7fb2c9ba57.bundle.js
注意
:注意webpack-dev-server的版本兼容问题,我webpack版本是"webpack": "4.39.2"
,webpack-cli版本是"webpack-cli": "3.3.7"
,webpack-dev-server版本是"webpack-dev-server": "3.8.0"
,能够正常启动服务,但又会出现下面报错:
TypeError: Cannot read property 'headers' of null
如下图报错:
把webpack-dev-server
版本改为"webpack-dev-server": "3.7.0"
,则不会再出现以上错误。
4. 处理css/scss样式文件
4.1 安装相关依赖
webpack不能直接处理css,需要借助loader。如果是.css,我们需要的loader通常有:style-loader、css-loader考虑到兼容性问题,还需要postcss-loader,而如果是less或者是sass的话,还需要less-loader和sass-loader,这里配置一下sass和css文件(less的话,使用less-loader即可):
安装相关依赖:
- css-loader、sass-loader 处理css样式和scss样式
- style-loader css-loader、sass-loader处理的样式结果通过style-loader处理成DOM里的style标签
- mini-css-extract-plugin 抽离CSS,即将CSS文件单独打包,这可能是因为打包成一个JS文件太大,影响加载速度,也有可能是为了缓存,使用该插件的,则会替代style-loader的功能
命令如下:
yarn add style-loader sass-loader css-loader mini-css-extract-plugin node-sass -D
4.2 webpack配置修改
webpack代码配置如下:
//webpack.config.dev.js
// 将CSS文件单独打包
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
//...
module: {
rules: [
{
test: /\.(sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === 'development'
}
},
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
'sass-loader'
],
exclude: /node_modules/
}
]
}
}
注意
:mini-css-extract-plugin
版本兼容配置问题,如果webpack的配置文件在mini-css-extract-plugin的配置方式如下:
而安装的mini-css-extract-plugin
版本为"mini-css-extract-plugin": "^1.3.1"
则启动服务后会报如下错误:
把mini-css-extract-plugin的配置方式如下:
启动服务则会报如下错误:
以上问题是mini-css-extract-plugin版本不兼容问题,使用"mini-css-extract-plugin": "1.2.1"版本,则不会出现以上问题。
5. 图片/字体文件处理
使用url-loader处理图片/字体,url-loader依赖file-loader,url-loader
可以指定在文件大小小于指定的限制时,返回 DataURL
5.1 webpack配置文件的修改
//webpack.config.dev.jsmodule.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|woff|svg|eot|ttf)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
esModule: false
name: '[name]_[hash:6].[ext]',
outputPath: 'assets'
},
}],
exclude: /node_modules/
},
],
},
};
5.2安装相关依赖
安装命令如下:
yarn add url-loader file-loader -D
注意
:如果项目运行或者打包后,图片文件路径错误输出为[object-module]
,则需要设置esModule: false
6. 更多相关webpack配置
6.1 每次打包前清空build目录
1. 安装依赖:
yarn add clean-webpack-plugin -D
2. webpack配置文件代码如下:
//webpack.config.dev.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
//...
plugins: [
//不需要传参数,它可以找到 outputPath
new CleanWebpackPlugin()
]
}
3. 希望build目录下某个文件夹不被清空
clean-webpack-plugin
为我们提供了参数 cleanOnceBeforeBuildPatterns
webpack配置文件代码如下:
//webpack.config.dev.js
module.exports = {
//...
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns:['**/*', '!dll', '!dll/**'] //不删除dll目录下的文件
})
]
}
6.2 静态资源拷贝到public
场景:有些时候,我们需要使用已有的JS文件、CSS文件(本地文件),但是不需要 webpack
编译。例如,我们在 public/index.html
中引入了 public
目录下的 js
或 css
文件。这个时候,如果直接打包,那么在构建出来之后,肯定是找不到对应的 js
/ css
。
对于这个问题,我们可以通过copy-webpack-plugin
拷贝至构建目录,然后在配置 CleanWebpackPlugin
时,注意不要清空对应的文件或文件夹即可。
1. 安装依赖
yarn add copy-webpack-plugin -D
2. webpack配置文件代码如下:
//webpack.config.dev.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
new CleanWebpackPlugin(
cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'build', 'js')],
),
new CopyWebpackPlugin([
{
from: 'public/js/*.js',
to: path.resolve(__dirname, 'build', 'js'),
flatten: true,
},
//还可以继续配置其它要拷贝的文件
])
]
}
注意
:flatten
这个参数,设置为 true
,那么它只会拷贝文件,而不会把文件夹路径都拷贝上
3. 如果我们要拷贝一个目录下的很多文件,但是想过滤掉某个或某些文件,那么 CopyWebpackPlugin
还为我们提供了 ignore
参数。
webpack配置文件代码如下:
//webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
new CopyWebpackPlugin([
{
from: 'public/js/*.js',
to: path.resolve(__dirname, 'build', 'js'),
flatten: true,
}
], {
ignore: ['other.js']
})
]
}
6.3 自动加载模块之ProvidePlugin
ProvidePlugin
的作用就是不需要 import
或 require
就可以在项目中到处使用。
ProvidePlugin
是 webpack
的内置插件,使用方式如下:
new webpack.ProvidePlugin({
identifier1: 'module1',
identifier2: ['module2', 'property2']
});
默认寻找路径是当前文件夹 ./**
和 node_modules
,你可以指定全路径。
1. webpack配置代码如下:
const webpack = require('webpack');
module.exports = {
//...
plugins: [
new webpack.ProvidePlugin({
React: 'react',
Component: ['react', 'Component'],
Vue: ['vue/dist/vue.esm.js', 'default'],
$: 'jquery',
_map: ['lodash', 'map']
})
]
}
这样配置之后,你就可以在项目中随心所欲的使用 $
、_map
了,并且写 React
组件时,也不需要 import
React
和 Component
了,如果你想的话,你还可以把 React
的 Hooks
都配置在这里。
注意
:Vue
的配置后面多了一个 default
,这是因为 vue.esm.js
中使用的是 export default
导出的,对于这种,必须要指定 default
。React
使用的是 module.exports
导出的,因此不要写 default
。
2. 全局变量定义在eslint相关配置文件的设置
如果你项目启动了 eslint
的话,记得修改下 eslint
的配置文件,增加以下配置:
在.eslintrc.js
配置文件的设置:
{
"globals": {
"React": true,
"Vue": true,
//....
}
}
6.4 定义全局变量之DefinePlugin
DefinePlugin
允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。DefinePlugin
是webpack
的内置插件。
1. 使用场景:
-
如果在开发构建中,而不在发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。
-
我们在开发环境中会使用预发环境或者是本地的域名,生产环境中使用线上域名,我们可以在
webpack
定义环境变量,然后在代码中使用。
2. 用法
每个传进 DefinePlugin
的键值都是一个标志符或者多个用 .
连接起来的标志符。
- 如果
value
是一个字符串,它会被当做code
代码片段 - 如果
value
不是一个字符串,它会被stringify
,会被转化为字符串(包括函数) - 如果
value
是一个对象,正常对象定义即可。即它所有的 key 会被同样的方式定义 - 如果
key
中有typeof
,它只针对typeof
调用定义。即如果在一个key
前面加了typeof
,它会被定义为typeof
调用
3. 代码使用如下:
+ const { DefinePlugin } = require('webpack');
+ const definePlugin = new DefinePlugin({
+ _DEV_: false,
+ GLOBAL_SERVICE: {
+ HOST: JSON.stringify('https://www.qianyin925.com:4000'),
+ GRAPHQL_URL: JSON.stringify('/graphql'),
+ },
+ });
module.exports = {
plugins: [
+ definePlugin,
]
};
6.5 resolve 配置
resolve
的作用:配置 webpack
如何寻找模块所对应的文件。
webpack
内置 JavaScript
模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你可以根据自己的需要修改默认的规则。
1. module
resolve.modules
配置 webpack
去哪些目录下寻找第三方模块,默认情况下,只会去 node_modules
下寻找,如果你我们项目中某个文件夹下的模块经常被导入,不希望写很长的路径,那么就可以通过配置 resolve.modules
来简化。
//webpack.config.dev.js
module.exports = {
//....
resolve: {
modules: ['./src/components', 'node_modules'] //从左到右依次查找
}
}
这样配置之后,我们 import Dialog from 'dialog'
,会去寻找 ./src/components/dialog
,不再需要使用相对路径导入。如果在 ./src/components
下找不到的话,就会到 node_modules
下寻找。
2. alias
resolve.alias
配置项通过别名把原导入路径映射成一个新的导入路径,例如:
//webpack.config.dev.js
module.exports = {
//....
resolve: {
alias: {
"@": path.resolve(__dirname, '../src'),
'react-native': '@my/react-native-web' //这个包名是随便写的
}
}
}
3. extensions
适配多端的项目中,可能会出现 .web.js
, .wx.js
,例如在转web的项目中,我们希望首先找 .web.js
,如果没有,再找 .js
。我们可以这样配置:
//webpack.config.dev.js
module.exports = {
//....
resolve: {
extensions: ['web.js', '.js'] //当然,你还可以配置 .json, .css
}
}
例如引入以下代码:
import dialog from '../dialog';
首先寻找 ../dialog.web.js
,如果不存在的话,再寻找 ../dialog.js
。这在适配多端的代码中非常有用,否则,你就需要根据不同的平台去引入文件(以牺牲了速度为代价)。
当然,配置extensions
我们就可以缺省文件后缀,在导入语句没带文件后缀时,会自动带上extensions
中配置的后缀后,去尝试访问文件是否存在,因此要将高频的后缀放在前面,并且数组不要太长,减少尝试次数。如果没有配置 extensions
,默认只会找对对应的js文件。
4. enforceExtension
如果配置了 resolve.enforceExtension
为 true
,那么导入语句不能缺省文件后缀。
5. mainFields
有一些第三方模块会提供多份代码,例如 bootstrap
,可以查看 bootstrap
的 package.json
文件:
{
"style": "dist/css/bootstrap.css",
"sass": "scss/bootstrap.scss",
"main": "dist/js/bootstrap",
}
resolve.mainFields
默认配置是 ['browser', 'main']
,即首先找对应依赖 package.json
中的 brower
字段,如果没有,找 main
字段。
如:import 'bootstrap'
默认情况下,找得是对应的依赖的 package.json
的 main
字段指定的文件,即 dist/js/bootstrap
。
但假设我们希望,import 'bootsrap'
默认去找 css
文件的话,可以配置 resolve.mainFields
为:
//webpack.config.dev.js
module.exports = {
//....
resolve: {
mainFields: ['style', 'main']
}
}
6.6 cross-env 设置环境变量
优点: 兼容多个平台
1. package.json的script命令设置如下:
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
"build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.prod.js" }
}
2. 安装命令如下:
yarn add cross-env -D
6.7 raw-loader 用于加载文本内容(.txt、.md 文件)
一个可以用于加载文件作为字符串使用的加载器,使用UTF-8编码。
1. 安装命令如下:
yarn add raw-loader -D
2. webpack配置文件如下:
module.exports = {
module: {
rules: [
{
test: /\.(text|md)$/,
use: 'raw-loader',
},
]
}
};
引入方式如下:
例如在file.js文件里
import txt from './file.txt';
6.8 区分不同的环境之webpack-merge使用
对于webpack.config.js
配置文件,当我们需要区分是开发环境还是生产环境的情况时,好的做法是创建多个配置文件,如: webpack.config.base.js
、webpack.config.dev.js
、webpack.config.prod.js
。
webpack.config.base.js
定义公共的配置webpack.config.dev.js
:定义开发环境的配置webpack.config.prod.js
:定义生产环境的配置
webpack-merge 专为 webpack
设计,提供了一个 merge
函数,用于连接数组,合并对象。
1. webpack-merge
安装命令如下:
yarn add webpack-merge -D
2. webpack-merge
的作用如下示例:
const merge = require('webpack-merge');
merge({
devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{a: 1}
]
},
plugins: [1,2,3]
}, {
devtool: 'none',
mode: "production",
module: {
rules: [
{a: 2},
{b: 1}
]
},
plugins: [4,5,6],
});
//合并后的结果为
{
devtool: 'none',
mode: "production",
module: {
rules: [
{a: 1},
{a: 2},
{b: 1}
]
},
plugins: [1,2,3,4,5,6]
}
其中,webpack.config.base.js
中是通用的 webpack
配置。
以 webpack.config.dev.js
为例,如下:
//webpack.config.dev.js
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.config.base');
module.exports = merge(baseWebpackConfig, {
mode: 'development'
//...其它的一些配置
});
对应 package.json
的script命令修改如下:
//package.json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.dev.js --open",
"build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.prod.js"
},
}
你可以使用 merge
合并,也可以使用 merge.smart
合并,merge.smart
在合并loader
时,会将同一匹配规则的进行合并,webpack-merge
的说明文档中给出了详细的示例。
6.9 webpack解决跨域问题
假设前端在3000端口,服务端在4000端口,我们通过 webpack
配置的方式去实现跨域。
1. 在 localhost:4000
上有后端服务的话,你可以这样启用代理:
proxy: {
"/api": "http://localhost:3000"
}
请求到 /api/users
现在会被代理到请求 http://localhost:4000/api/users
2. 如果你不想始终传递 /api
,则需要重写路径:
proxy: {
"/api": {
target: "http://localhost:3000",
pathRewrite: {"^/api" : ""}
}
}
3. 默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,修改配置如下:
proxy: {
"/api": {
target: "https://other-server.example.com",
secure: false
}
}
4. proxy
的参数列表中有一个changeOrigin
参数, 是一个布尔值, 设置为true
, 本地就会虚拟一个服务器接收你的请求并代你发送该请求
proxy: {
"/api": {
target: "https://other-server.example.com",
secure: false,
changeOrigin: true
}
}
6.10 热更新
1. 首先配置 devServer
的 hot
为 true
2. 并且在 plugins
中增加 new webpack.HotModuleReplacementPlugin()
代码如下:
//webpack.config.dev.js
const webpack = require('webpack');
module.exports = {
//....
devServer: {
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin() //热更新插件
]
}
6.11 多页应用打包方式
1. 场景:我们的应用不一定是一个单页应用,而是一个多页应用,那么如何使用 webpack
进行打包呢。
为了生成目录看起来清晰,不生成单独的 map
文件。
代码如下:
//webpack.config.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
login: './src/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:6].js'
},
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html' //打包后的文件名
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html' //打包后的文件名
}),
]
}
注意
:如果需要配置多个 HtmlWebpackPlugin
,那么 filename
字段不可缺省,否则默认生成的都是 index.html
,如果你希望 html
的文件名中也带有 hash
,那么直接修改 fliename
字段即可,例如: filename: 'login.[hash:6].html'
。
2. 生成目录如下:
.
├── build
│ ├── 2.463ccf.js
│ ├── assets
│ │ └── thor_e09b5c.jpeg
│ ├── css
│ │ ├── index.css
│ │ └── login.css
│ ├── index.463ccf.js
│ ├── index.html
│ ├── js
│ │ └── base.js
│ ├── login.463ccf.js
│ └── login.html
但在查看 index.html
和 login.html
会发现,都同时引入了 index.f7d21a.js
和 login.f7d21a.js
,通常这不是我们想要的,我们希望,index.html
中只引入 index.f7d21a.js
,login.html
只引入 login.f7d21a.js
。
HtmlWebpackPlugin
提供了一个 chunks
的参数,可以接受一个数组,配置此参数仅会将数组中指定的js引入到html文件中,此外,如果你需要引入多个JS文件,仅有少数不想引入,还可以指定 excludeChunks
参数,它接受一个数组。
代码如下:
//webpack.config.dev.js
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html', //打包后的文件名
chunks: ['index']
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html', //打包后的文件名
chunks: ['login']
}),
]
}
6.12 devtool
devtool
中的一些设置,可以帮助我们将编译后的代码映射回原始源代码。不同的值会明显影响到构建和重新构建的速度。
对我而言,能够定位到源码的行即可,因此,综合构建速度,在开发模式下,我设置的 devtool
的值是 cheap-module-eval-source-map
。
//webpack.config.js
module.exports = {
devtool: 'cheap-module-eval-source-map' //开发环境下使用
}
7. 优化项目的webpack配置
webpack打包优化的方式:多进程打包
、多进程压缩
、资源CDN
、动态polyfill
7.1 量化指标插件之speed-measure-webpack-plugin
speed-measure-webpack-plugin
插件可以测量各个插件和loader
所花费的时间,即速度测量插件,使用之后,构建时,会得到类似下面这样的信息:
这样可以对比前后的信息,来确定优化的效果。
安装命令如下:
yarn add speed-measure-webpack-plugin -D
speed-measure-webpack-plugin
的配置信息如下:
//webpack.config.dev.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
//...webpack配置
}
module.exports = smp.wrap(config);
如果使用webpack-merge
这个插件区分了不同的环境,则speed-measure-webpack-plugin
的配置信息如下:
//webpack.config.base.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
//...webpack配置
}
module.exports = smp.wrap(config);
7.2 体积分析之webpack-bundle-analyzer
分析完打包速度之后,接着我们来分析打包之后每个文件以及每个模块对应的体积大小。使用到的插件为 webpack-bundle-analyzer
,构建完成后会在 8888
端口展示大小。
借助 webpack-bundle-analyzer
查看一下是哪些包的体积较大。该依赖是在项目打包时使用。
1. 安装依赖命令:
yarn add webpack-bundle-analyzer -D
2. 配置文件代码配置如下:
//webpack.config.prod.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.config.base');
module.exports = merge(baseWebpackConfig, {
//....
plugins: [
//...
new BundleAnalyzerPlugin(),
]
})
3. 运行yarn run build
命令后打包自动启动http://127.0.0.1:8888/
端口,显示如下图:
7.3 webpack打包速度优化
1. 优化场景:
webpack构建过程中直接影响构建效率的,一个是文件的编译,另一个是文件的分类打包。相较之下文件的编译更为耗时,而且在Node环境下文件只能一个一个去处理,因此这块的优化需要解决。那么要怎样优化打包速度呢?
2. 使用高版本的webpack
和node.js
webpack4新版本的优化使用v8引擎,v8带来的优化包括:
- for of替代forEach
- Map和Set替代Object
- includes替代indexOf()
- 默认使用更快的md4 hash算法 替代 md5算法,md4较md5速度更快
- webpack AST 可以直接从loader传递给AST,从而减少解析时间
- 使用字符串方法替代正则表达式
更高版本的node.js对原生js api
和js数据结构
做出进一步的优化
3. 多进程/多实例构建(资源并行解析)
在webpack构建过程中,我们需要使用Loader对js,css,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大的,且这些转换操作不能并发处理文件,而是需要一个个文件进行处理,我们需要的**是将这部分任务分解到多个子进程中去并行处理
**,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间。
可选方案
- thread-loader(官方推出)
- parallel-webpack
- HappyPack
HappyPack
注意
:由于HappyPack作者对js的兴趣逐渐减少,所以之后维护将变少,webpack4及之后推荐使用thread-loader
原理
:每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker进程中;HappyPack会将模块进行一个划分,比如我们有多个模块,这些模块交给HappyPack,首先在webpack compiler(钩子)的run方法之后,进程就会到达HappyPack,**HappyPack会做一些初始化**
,初始化之后会**创建一个线程池**
,线程池会将构建任务里面的模块进行一个分配,比如会将某个模块以及它的一些依赖分配给其中的一个HappyPack线程,以此类推,那么一个HappyPack的**一个线程池会包括多个线程
,这时候线程池的这些线程会各自去处理其中的模块以及它的依赖,处理完成之后会有一个通信的过程,会将处理好的资源传输给HappyPack的一个_主进程_
**,完成整个的一个构建过程。
将src目录下复制出多个相同页面
在没引入HappyPack之前执行打包
HappyPack安装命令如下:
yarn add happypack -D
注意
:如果在webpack4使用需要HappyPack5.0的版本。
引入之后将rules对js的编译改为happypack/loader
webpack文件设置如下
rules: [
{
test: /\.(mjs|js|jsx)$/, use: 'HappyPack/loader?id=js',
exclude: /node_modules/
},
]
在插件中加入happypack-loader
plugins: [
new HappyPack({
id: 'js', //和rule中的id=js对应
threadPool: HappyPack.ThreadPool({size: 4}),
loader: [
{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
[
"@babel/plugin-transform-runtime",
{"corejs": 3}
]
]
}
}
]
})
]
thread-loader
原理
:与HappyPack类似,每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker进程中;
安装命令如下:
yarn add thread-loader -D
在rule中添加thread-loader,thread-loader可以进行一些配置,例如workers(进程数)
rules: [
{
test: /\.(mjs|js|jsx)$/,
use: {
'babel-loader',
{
loader: 'thread-loader',
options: {
workers: 3
}
}
},
exclude: /node_modules/
},
]
7.4 多进程/多实例进行代码压缩(并行压缩)
在代码构建完成之后输出之前有个代码压缩阶段,这个阶段也可以进行并行压缩来达到优化构建速度的目的;
可选方案:
- webpack-parallel-uglify-plugin
- uglifyjs-webpack-plugin(不支持es6语法)
- terser-webpack-plugin**(webpack4.0推荐使用,支持压缩es6代码)**
引入代码如下:
const TerserPlugin = require('terser-webpack-plugin');
optimization
配置如下:
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
//代码压缩插件
parallel: 4, //开启并行压缩
}),
],
},
注意
:在webpack配置文件设置好后,启动服务,会报
TypeError: Cannot read property 'javascript' of undefined
错误提示,原来是版本问题
如果是使用webpack4版本,则可以对应安装"terser-webpack-plugin": "^4.2.3"
,webpack5使用terser-webpack-plugin5.0以上版本。我现在安装的webpack版本是"webpack": "4.39.2"
则对应terser-webpack-plugin版本可以安装"terser-webpack-plugin": "^4.2.3"
,如果安装"terser-webpack-plugin": "^5.0.3"
,则报如下图错误:
7.5 通过缓存提升二次打包速度
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- 使用cache-loader或者 hard-source-webpack-plugin
1. 设置babel-loader的cacheDirectory=true开启缓存
new HappyPack({
loaders: ['babel-loader?cacheDirectory=true'],
})
2. 设置terser-webpack-plugin插件的cache: true开启缓存
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
//代码压缩插件
parallel: 4, //开启并行压缩
cache: true,
}),
],
},
3. HardSourceWebpackPlugin之为模块提供中间缓存
HardSourceWebpackPlugin
为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source
。
配置 hard-source-webpack-plugin
,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。
安装命令如下:
yarn add hard-source-webpack-plugin -D
webpack配置文件修改如下:
//webpack.config.base.js
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
//...
plugins: [
new HardSourceWebpackPlugin()
]
}
更改内容后第一次打包时间如下(还是比没加缓存前打包速度快):
不更改内容再次打包的时间如下:
7.5 缩小构建目标
尽可能的少构建模块,比如babel-loader不解析 node_modules
- 优化resolve.modules配置(减少模块搜索层级)
- 优化resolve.mainFields配置
- 优化resolve.extensions配置
7.6 打包体积的优化
主要对打包后图片、js、css文件的资源体积优化
1. 图片压缩
使用Node库的imagemin,配置image-webpack-loader
对图片优化,改插件构建时会识别图片资源,对图片资源进行优化
imagemin优点分析
- imagemin有很多定制选项
- 可以引入更多第三方优化插件,例如
pngquant
- 可以引入多种图片格式
imagemin的压缩原理
-
pngquant:是一款PNG的压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG文件小60%-80%)的更高效的8位PNG格式,可显著减小文件大小;
阿尔法通道(Alpha Channel)是指一张图片的透明和半透明度; -
pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据流的大小;
-
optipng:其涉及灵感来自于pngcrush。optipng可将图像文件重新压缩位更小的尺寸,而不会丢失任何信息;
-
tingpng:也是将24位png文件转化为更小具有索引的8位图片,同时所有非必要的metadata也会被剥离掉;
安装命令如下:
yarn add image-webpack-loader -D
注意
:该插件在使用yarn或者npm即使使用梯子也会安装报错,建议使用cnpm安装。
webpack配置文件如下:
rules: [
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]',
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65,
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75,
},
},
},
],
}
]
对比使用image-webpack-loader
插件的前后对比如下:
使用前:
使用依赖后:
对比使用image-webpack-loader
前后的文件大小,可显著减小文件大小,把大文件的图片压缩。
注意
:注意依赖的安装顺序,如果先安装好webpack-dev-server
以后安装image-webpack-loader
,不会出现什么问题,但如果两者都安装好再remove掉webpack-dev-server
后再安装webpack-dev-server
,启动服务,会报image-webpack-loader
的mozjpeg、
gifsicle等相关的错误,必须remove掉image-webpack-loader
,重新再安装就好了,不会再报错。
如果出现image-webpack-loader
安装不成功的情况,则使用 cnpm , 这一步意思就是安装 cnpm 然后将全局的 registry 设置成阿里的镜像,国内阿里比较快。
命令如下:
npm install cnpm -g --registry=https://registry.npm.taobao.org
使用 cnpm
安装 image-webpack-loader
会发现很快就安装好了
cnpm install --save-dev image-webpack-loader
注意:如果先前尝试使用 yarn
或 npm
安装过image-webpack-loader
,一定要先remove卸载掉用再使用 cnpm
安装。
2. 擦除没有用到的css
可以通过插件遍历代码,识别已经用到的css class
安装命令如下:
yarn add purgecss-webpack-plugin -D
注意
:该插件在使用yarn或者npm即使使用梯子也会安装报错,建议使用cnpm安装。
webpack配置文件如下:
const path = require('path');
const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');
const PATHS = {
src: path.join(__dirname, 'src')
}
plugins: [
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
})
]
设置多个路径:
如果你需要设置多个路径,请使用 npm
软件包 glob-all
来替换 glob
,然后就可以使用以下语法了:
new PurgecssPlugin({
paths: glob.sync([
// ...
])
}),
7.7 babel-polyfill的使用
8. 引入 Antd 并配置按需加载
为 .babelrc
配置添加插件 babel-plugin-import
从而实现 antd 的按需加载。
8.1 修改.babelrc
注意
: 配置插件时可以设置实例化名称 import-antd
, 这样就可以多次使用同一插件, 如果你还需要使用 babel-plugin-import
处理其他组件库
{
+ "plugins": [
+ ["import", {
+ "libraryName": "antd",
+ "libraryDirectory": "es",
+ "style": "css"
+ }, "import-antd"]
+ ],
"presets": [
"@babel/preset-react",
"@babel/preset-env"
]
}
8.2 相关依赖安装
yarn add antd
yarn add babel-plugin-import -D
8.3 代码引入示例
import React from 'react';
import reactDom from 'react-dom';
+ import { Button } from 'antd';
const App = () => (
<div>
+ <Button type="primary">按钮</Button>
</div>
);
reactDom.render(<App/>, document.getElementById('root'));
注意
:在引入antd后运行项目报如下错误:
ERROR in ./node_modules/antd/es/button/style/index.css 5:0
Module parse failed: Unexpected token (5:0)
原因如下:在webpack配置文件里css的配置部分多了exclude: /node_modules/,这样导致编译css文件时不会编译antd部分,导致报错,如下图: