webpack3配置
npm install webpack@3 -g #安装最新的3.x版本的webpack
npm install webpack-cli@2 -g
命令行下webpack常见配置参数
- --config 设置配置文件
- -p 打包压缩
--progress 打印打包进度 - -watch, -w 监听文件变化
- --entry 指定入口文件
- --hot 开启热更新
- webpack entry output 指定入口文件和打包后的文件
1.0 编译ES6配置
"devDependencies": {
"@babel/core": "^7.6.2",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/preset-env": "^7.6.2",
"@babel/runtime": "^7.6.2",
"babel-loader": "^8.0.0-beta.0",
"babel-polyfill": "^6.26.0"
}
使用webpack配置文件方式编译ES6
import "babel-polyfill"
...
module:{
rules:[{
test:/\.js$/,
exclude:"/node_modules/",
use:{
loader:"babel-loader",
options:{
presets:[
["@babel/preset-env",{
targets:{
browsers:["last 2 versions","> 1%"]
}
}]
]
}
}
}]
}
使用babel配置文件的方式
{
"presets":[["@babel/preset-env",{
"targets":{
broswers:["last 2 versions","> 1%"]
}
}]],
"plugins":["@babel/transform-runtime"]
}
想请教一下,既然babel-runtime和plugin-transform-runtime也可以使项目支持ES6新增API,而且压缩文件那么小,那生产环境中是否只用babel-polyfill就可以啦呢?我之前看的视频说babel-polyfill在生产环境下用,而runtime用在一个框架库上,这是为何?
1.2 编译typescript
- 安装依赖
npm install typescript ts-loader awesome-typescript -D
- 创建tsconfig.json
{
"compilerOption":{
"module":"commonjs",
"target":"es5",
"allowJs":true,
"typeRoot":[
"./node_modules/@types",
"./typings/modules"
]
},
"include":["./src/*"],
"exclude":["./node_modules"]
}
- 使用库文件的声明文件
1.npm install @types/ts写的库
2.npm install typing && typing install ts写的库文件
**会在项目目录生成typings的文件夹,里面是库文件的声明文件**
1.3 公共代码提取
pageA,B分别引入subPageA,B,而subPageA,B又同时引入module.js文件和第三方库lodash.CommonschunksPlugins只适用于多entry的情况,所以需要多的entry
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry:{
PageA:"./src/CommonChunk/pageA.js",
PageB:"./src/CommonChunk/pageB.js",
vendor:["lodash"]
},
output:{
path:path.resolve(__dirname,"./dist/"),
filename:"[name].bundle.js",
chunkFilename:"[name].chunk.js"//非入口(non-entry) chunk 文件的名称
},
plugins:[
// //chunks指定提取的公共部分的范围,否则如果下面的lodash提取的时候(infinity)报错
new webpack.optimize.CommonsChunkPlugin({
name:"common",
minChunks:2,//当大于等于minChunks设定的值时,该模块就会被打包到公用包中
chunks:["PageA","PageB"]//指定它提取代码的范围
}),
//会将lodash用webpack打包提取出来,里面包含webpack的代码和lodash的代码
// new webpack.optimize.CommonsChunkPlugin({
// names:["vender"]
// minChunks:Infinity
// }),
// 会将loadash和webpack自己的代码打包到vender-chunk文件中,但是不包括pageA和pageB的公共部分
new webpack.optimize.CommonsChunkPlugin({
//相当于将第二次的非lodash代码单独提取到manifest.bundle.js文件中
names:["vendor","mainfest"],
minChunks:Infinity
}),
]
}
1.4 代码分割&懒加载
懒加载require.ensure
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry:{
PageA:"./src/CommonChunk/pageA.js",
PageB:"./src/CommonChunk/pageB.js",
vendor:["lodash"]
},
output:{
path:path.resolve(__dirname,"./dist/"),
filename:"[name].bundle.js",
publicPath:"./dist/",
chunkFilename:"[name].chunk.js"
},
plugins[
new webpack.optimize.CommonsChunkPlugin({
//会将pageA/B的懒加载公共部分单独提取到async-common.chunk.js中
async:"async-common",
children:true,//就不需要chunks配置
minChunks:2//当大于等于minChunks设定的值时,该模块就会被打包到公用包中
}),
new webpack.optimize.CommonsChunkPlugin({
name:"common",
minChunks:2,//当大于等于minChunks设定的值时,该模块就会被打包到公用包中
chunks:["PageA","PageB"]//指定它提取代码的范围
})
]
}
import "./moduleA";
export function subPageA(a,b){
return a+b;
}
// 对pageAB进行懒加载,只有需要的的时候才require
require.ensure(["./subPageA"],function(){
var A = require("./subPageA");
A.subPageA(11,33)
},"subPageA");#会生成subPageA.chunk.js的异步提取文件
懒加载之import
package.json和.babelrc需要额外安装才能支持import动态加载
"babel-plugin-syntax-dynamic-import": "^6.18.0"
"plugins":["syntax-dynamic-import"]
//pageA
import(/* webpackChunkName:'subA' */"./subPageA")
.then(function(subA){
console.log("subA=====>",subA)
});
1.5 样式处理
{
"scripts": {
"build": "webpack -p --progress --config webpack.conf.js"
},
"dependencies": {
"css-loader": "^1.0.0",
"cssnano": "^4.1.4",
"extract-text-webpack-plugin": "^3.0.2",
"lodash": "^4.17.11",
"postcss": "^7.0.5",
"autoprefixer": "^9.1.5",
"postcss-cssnext": "^3.1.0",
"postcss-loader": "^3.0.0",
"purify-css": "^1.2.5",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.0"
},
"devDependencies": {
"glob-all": "^3.1.0",
"node-sass": "^4.12.0",
"purifycss-webpack": "^0.7.0",
"webpack": "^3.5.6"
},
//让css和js都使用该配置统一浏览器
"browserslist": [
"last 2 versions",
">= 1%"
]
}
const path = require("path");
const webpack = require("webpack");
const purifyCSS = require("purifycss-webpack");
// 处理多路径的css,比如说js,html中的
const glob = require("glob-all");
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
module.exports = {
module:{
rules:[{
test:/\.scss$/,
use:ExtractTextWebpackPlugin.extract({
fallback:{
loader:"style-loader",
options:{
// insertInto:"#Box",
// 使插入到app节点的样式显示为一个style标签
singleton:true,
transform:"./src/css.transform.js"
}
},
use:[{
loader:"css-loader",
options:{
// minimize:false,//已经被其他替换,这里不起作用,请看github issue
modules:true,
localIdentName:"[path][name]_[local]--[hash:base64:3]"
}
},{
loader:"postcss-loader",
options:{
ident:"postcss",
plugins:[
// require("autoprefixer")(),//cssnext已经存在
require("postcss-cssnext")()
]
}
},{
loader:"sass-loader"
}
]})
}]
},
plugins:[
new ExtractTextWebpackPlugin({
filename:"[name].min.css",
allChunks:false//只提取初始化的样式,而不是异步加载的样式(ensure,import)
}),
// new purifyCSS({
// paths:glob.sync([
// // 针对html,js里面添加的class进行全局查找,csss用上的就提取,没用上的不提取
//
// path.join(__dirname,"./*.html"),
// path.join(__dirname,"./src/**/*.js")
// ])
// })
]
}
css tree shaking一定要在extract之后,但是开启css-loader module之后purifyCSS就有点问题
PurifyCSS doesn't support classes that have been namespaced with CSS Modules. However, by adding a static string to css-loader's localIdentName, you can effectively whitelist these namespaced classes.
https://github.com/webpack-contrib/purifycss-webpack
JS tree shaking 使用
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true
...
},
})
1.6打包其他资源文件
- file-loader处理图片编译
- url-loader图片base64处理
- img-loader图片压缩
- postcss-sprites生成精灵图层
1.7 第三方资源引用配置
resolve:{
alias:{
#针对自定义的第三方库路径
lodash$:path.resolve(__dirname,"./lib/lodash/lodash.min.js")
}
},
plugins:[
new webpack.ProvidePlugin({
#key:自定义变量,val:指向node_modules中的模块名
$:"jquery",
_:"lodash"
})
]
1.8 自动生成HTML
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlInlineChunkPlugin = require("html-inline-chunk-plugin");
...
entry:{
app:"./src/app.js",
main:"./main.js"
vendor:["jquery"]
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor','runtime'],
filename: '[name].js',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
filename: '[name].js',
chunks: ['app','main']#抽取commons chunk
}),
#用于将webpack运行时的公共代码直接插入到html页面,防止多余的http请求产生
new HtmlInlineChunkPlugin({
inlineChunks:["runtime"]
}),
new HtmlWebpackPlugin ({
filename:"index.html",
template:"./index.html",
inject:true,#将打包生成的css和js插入到html中
chunks:["app"]#针对多entry的只插入指定的entry chunks
minify:{
collapseWhitespace:true
}
})
]
1.9 模块热更新配置
- historyApiFallback:单页面应用网地址栏输入路由的时候会自动跳转 到index.html,否则会报404
- port 端口
- hot 热更新配置
- overlay: true,出现错误之后会在页面中出现遮罩层提示
- inline 在页面显示出更新信息,默认为true,不显示更新信息
- proxy http代理
- target
- changeOrigin
- header设置代理的请求头
- pathRewrite
#当前是在127.0.0.1:4000端口
# $.get("http://localhost:4000/api/blog")
devServer:{
port :3000,
proxy:{
"/":{
target:"http:www.myblog.com",
changeOrigin:true,
logLevel:"debug"#会在终端输出代理信息,
headers:{
"cookie":"..." #使用该用户信息登录
},
pathRewrite:{
"^/blog":"/api/blog" #
}
}
}
}
# pathRewrite的结果" localhost:4000/blog=> localhost:4000/api/blog=>http:www.myblog.com/api/blog
在plugins中加入以下插件即可,而css是通过style-loader实现热更新,所以如果是通过提取的方式是无法实现热更新
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
#在js代码主入口文件中需要加入
if(module.hot){
module.hot.accept(moduleId, callback);
}
但是一般的react和vue的loader都会帮我们处理js代码中的上述步骤
2.0 开启开发调试
css开发调试需要在css的每个loader中加上sourceMap:true属性
devtool:"cheap-module-surce-map"
...
new webpack.optimize.UglifyJsPlugin({sourceMap: true})
2.1 区分生产环境和开发环境
开发环境需要的内容
- 代码热更新
- sourceMap(devtool)
- 代码规范检查(ESLint)
- 服务器器代理(webpack-dev-server)
生产环境需要的内容
- 公用代码提取(extract-text-webpack-pluginpurifycss-webpack)
- 去除无用代码(Tree shaking)
- 文件压缩(img-loader,url-loader)
- 压缩混淆(webpack.optimize.UglifyJsPlugin)
2.2 开发和生产环境配置
create-react-app配置热更新
- 安装react-hot-loader
cnpm i react-hot-loader -D
- webpack加入配置
entry: [
#放在babel-polyfill之后,其他项之前
"react-hot-loader/patch"
],
devServer: {
hot: true,
...
},
plugins:[
new webpack.hotModuleReplacementPlugin()
...
]
- 在.babelrc中添加plugin
"plugins": ["react-hot-loader/babel"]
- 入口文件配置
import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader"
import App from "./components/App/";
const render = Component => {
ReactDOM.render(
,
document.getElementById("root")
);
}
render(App);
if(module.hot){
module.hot.accept("./components/App/",()=>{
render(App)
})
}
自己搭建react HMR环境
https://segmentfault.com/a/1190000011151106
Webpack4 打包配置
1.0 HTML资源内联
${require(raw-loader!babel-loader+资源的路径)}
1.1 资源压缩
在mode为production的情况下js默认打包压缩,如果还需要其他压缩我配置需要安装uglifyjs-webpack-plugin
HTML压缩和webpack3一致
new HtmlWebpackPlugin({
template:path.join(__dirname,"./index.html"),
inject:true,
filename:"index.html",
minify:{
removeComments:true,
collapseWhitespace:true
}
}),
css压缩配置
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css\.*(?!.*map)/g, //注意不要写成 /\.css$/g
cssProcessor: require('cssnano'),
cssProcessorOptions: {
discardComments: { removeAll: true },
// 避免 cssnano 重新计算 z-index
safe: true,
// cssnano 集成了autoprefixer的功能
// 会使用到autoprefixer进行无关前缀的清理
// 关闭autoprefixer功能
// 使用postcss的autoprefixer功能
autoprefixer: false
},
canPrint: true
}),
1.2 公共提取
(待更新...)
1.3 Tree Shaking
JS Tree Shaking
webpack4下JS TreeShaking 默认打开,前提是js代码是ES6语法写的,方法没有副作用(没有引用到全局或者函数外部的变量,单一的输入有单一的输出),同时mode为production
CSS Tree Shaking
#webpack3
new purifyCSS({
paths:glob.sync([
# 针对html,js里面添加的class进行全局查找,csss用上的就提取,没用上的不提取
path.join(__dirname,"./*.html"),
path.join(__dirname,"./src/**/*.js")
])
});
#webpack4
new purgecss-webpack-plugin({
#内容同上,但是需要配合mini-css-extract-plugin一起使用
})
1.4 Scope Hoisting
原理:将所有代码按照模块引用顺序放在一个函数作用域里面,然后还适当的重命名一些变量,防止变量名冲突。通过Scope Hoisting可以减少函数声明和内存开销(因为没有模块都是以闭包形式的包裹)。
开启Scope Hoisting的方式mode="production"或者手动引入webpack.optimize.ModuleConcatenationPlugin,该插件是在production自动会引入的,production情况下有很多其他插件都是默认打开的
Sets process.env.NODE_ENV on DefinePlugin to value production . Enables FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin and TerserPlugin .
1.5 webpack4 集成ESLint代码检查规范
- 安装依赖
npm install eslint
eslint-plugin-import
eslint-plugin-react
eslint-plugin-react-hooks
eslint-plugin-jsx-a11y
eslint-loader
babel-eslint(eslint解析器) -D
- 在webpack解析JS的babel-loader后面加入eslint-loader
- 创建eslint配置文件
module.exports = {
"parser":"babel-eslint",
"exnteds":"airbnb",
"env":{
"brower":true,
"node":true
},
rules:{
...
}
}
EsLint官方指南
eslint-config-airbnb
1.6 打包自己的组件和库
- 创建webpack.prod.js文件配置
const TerserPlugin = require('terser-webpack-plugin') // 引入压缩插件
module.exports = {
entry: {
'myLibrary': './src/index.js',
'myLibrary.min': './src/index.js'
},
output: {
filename: '[name].js',
library: 'myLibrary',#对外输出的模块名,指定库的全局变量
libraryTarget: 'umd',
libraryExport: 'default' #可以直接 new myLibrary(),而不是new myLibrary().default;
},
mode: 'none', // 设置mode为none避免默认压缩
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({ // 使用压缩插件,只压缩min结尾的
include: /\.min\.js$/
})
]
}
}
- 编写自己库的代码
- 根据package.json中main字段指定的文件定义不同开发环境下引用的不同库打包文件
if (process.env.NODE_ENV === 'production') { // 通过环境变量来决定入口文件
module.exports = require('./dist/myLibrary.min.js')
} else {
module.exports = require('./dist/myLibrary.js')
}
1.7 发布库或组件到npm
- 注册npm账户
- 终端输入 npm login
- 输入注册的账户&密码
- npm publish
npm包升级
- npm version patch =>#v1.0.1
- npm version minor =>#v1.1.0
- npm version major =>#v2.0.0
1.8 服务端渲染(SSR)打包配置
(带更新...)
1.9 构建日志的处理
npm install friendly-errors-webpack-plugins -D
#webpack.dev.config.js
devServer:{stats:"errors-only"}
#webpack.prod.config.js
#直接添加一个一段
stats:"errors-only"
2.0 单元测试(mocha & jest)
By default, mocha looks for the glob "./test/*.js", so you may want to put your tests in test/ folder. If you want to include subdirectories, pass the --recursive option.
To configure where mocha looks for tests, you may pass your own glob:
mocha --recursive "./spec/*.js"或者
mocha "./spec/**/*.js"
Mocha
需要测试方法函数
#mocha/demoCase/demo.js
module.exports = function (...rest) {
var sum = 0;
for (let n of rest) {
sum += n;
}
return sum;
};
每个it("name", function() {...})就代表一个测试
#mocah/demoCase/test/demo-test.js
const assert = require('assert');
const sum = require('../demoCase/demo');
describe('mode.js', () => {
describe('#sum()', () => {
it('sum() should return 0', () => {
assert.strictEqual(sum(), -1);
});
it('sum(1) should return 1', () => {
assert.strictEqual(sum(1), 1);
});
});
});
#package.json
"script":{
"test":"mocha"
}
测试覆盖率
测试的时候,我们常常关心,是否所有代码都测试到了。
这个指标就叫做"代码覆盖率"(code coverage),它有四个测量维度。
- 行覆盖率:是否每一行都执行了?
- 函数覆盖率:是否每个函数都调用了?
- 分支覆盖率:是否每个if代码块都执行了?
- 语句覆盖率:是否每个语句都执行了?
npm install istanbul -D
#package.json
"test":"istanbul cover ./node_modules/bin/_mocha"
https://github.com/gotwarlost/istanbul
阮一峰:代码覆盖率工具 Istanbul 入门教程
Jest
(待更新...)
2.1 持续集成Travis CI
持续集成服务 Travis CI 教程-阮一峰
how-to-use-travis-ci
Building a JavaScript and Node.js project in travis CI
2.2 错误处理与捕获
#在plugin中添加一个错误捕获插件
function() {
this.hooks.done.tap('done', (stats) => {
if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') == -1) {
console.log('build error');
process.exit(1);
}
})
}
2.3 打包性能分析
webpack可视化打包性能分析插件
2.4 打包优化方案
- 预编译资源模块 DllPlugin 和DllReferencePlugin
#webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
reactLib: [
'react',
'react-dom'
]
},
output: {
filename: '[name]_[chunkhash:4].dll.js',
path: path.join(__dirname, 'dll/lib'),
library: '[name]_dll' #var reactLib_dll=function(e){var t={}...
},
plugins: [
new webpack.DllPlugin({
name: '[name]_dll',#同上library值
path: path.join(__dirname, 'dll/lib/[name].json')
})
]
};
npm run build:dll之后会生成reactLib.json和[name]_[chunkhash:4].dll.js文件
"build:dll":"cross-env NODE_ENV=production webpack -p --config webpack.dll.config.js"
# webpack.prod.config.js
new webpack.DllReferencePlugin({
manifest: require('./build/library/library.json')
})
在prod文件plugin字段中添加引用,执行npm run build即可,npm run build:dll只需要执行一遍就行
- 缩小构建文件查找的范围
- 置loader时,通过test、exclude、include缩小搜索范围
- 合理使用resolve字段
- 设置resolve.modules:[path.resolve(__dirname, 'node_modules')]避免层层查找
- 设置resolve.mainFields:['main'],设置尽量少的值可以减少入口文件的搜索步骤
- 开启构建缓存,提升构建速度
- hard-source-webpack-plugin
- 设置tester-webpack-plugin cache为true
- babel-loader?cacheDirectory=true
- 多进程多实例并行构建
- happypack webpack3 解决方案
const HappyPack = require('happypack');
exports.module = {
rules: [
{
test: /.js$/,
// 1) replace your original list of loaders with "happypack/loader":
// loaders: [ 'babel-loader?presets[]=es2015' ],
use: 'happypack/loader',
include: [ /* ... */ ],
exclude: [ /* ... */ ]
}
]
};
exports.plugins = [
// 2) create the plugin:
new HappyPack({
// 3) re-add the loaders you replaced above in #1:
loaders: [ 'babel-loader?presets[]=es2015' ]
})
];
thread-loader webpack4解决方案
-
多进程多实例并行压缩
- new webpack.optimize.UglifyJsPlugin webpack3解决方案,设置parallel和cache为true并且关闭生产环境下的sourceMap
- webapck-parallel-webpack-plugin 默认开启并行压缩
- uglify-webpack-plugin 设置parallel为并行数
- tester-webpack-plugin 设置parallel为某并行数
-
长缓存优化
- 提取vendor
- hash->chunkHash
- 提取webpack runtime
- 异步加载的模块自定义chunkName
- chunk: 是指代码中引用的文件(如:js、css、图片等)会根据配置合并为一个或多个包,我们称一个包为 chunk。
- module: 是指将代码按照功能拆分,分解成离散功能块。拆分后的代码块就叫做 module。可以简单的理解为一个 export/import 就是一个 module。
在我们引入其他模块的时候模块顺序变化,vendor hash也会变化,解决方案:new NamedModulesPlugin 固定moduleID
而在动态加载的模块过程中也会发生vendor hash改变的情况,解决方案:new NamedChunksPlugin 固定chunkID
import(/* webpackChunkName: "my-chunk-name" */ "module");
基于webpack4[.3+]构建可预测的持久化缓存方案
对于lodash
这种第三方库,正确的用法是只去import
所需的函数(用什么引什么),例如:
// 正确用法`
import isPlainObject from 'lodash/isPlainObject'
//错误用法`
import { isPlainObject } from 'lodash'
这两种写法的差别在于,打包时webpack会根据引用去打包依赖的内容,所以第一种写法,webpack只会打包lodash的isPlainObject库,第二种写法却会打包整个lodash。现在假设在项目中只是用到不同模块对lodash里的某几个函数并且没有对于某个函数重复使用非常多次,那么这时候把lodash添加到dll中,带来的收益就并不明显,反而导
动态polyfill
https://c7sky.com/polyfill-io.html
webapck面试集
https://cloud.tencent.com/developer/article/1356611