1 什么是webpack
构建就是把源代码转换成线上可执行的js、css、html
代码分割,提取多个页面的公共代码,提取首屏不需要执行的代码异步加载,路由按需加载
代码校验,在代码提交到仓库前校验代码,以及单元测试是否通过
自动发布,更新完代码后,自动构建出现上发布代码并传输给发布系统
利用CDN加速,在构建过程中,将引用的静态资源路径修改为CDN上对应的路径
2 核心概念
entry:入口,可抽象成输入,单入口写成字符串,多入口写成对象
output:输出结果,在webpack经过一系列处理并得出最终想要的代码后输出结果,单出口filename和path,多出口使用占位符[name].js
loader:模块转换器,用于把原生不支持的模块转换成有效的模块,并添加到依赖图中,原生只支持js和json,其本质是一个函数
babel-loader
css-loader
less-loader
ts-loader
file-loader
raw-loader
thread-loader
plugin:在Webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情,增强webpack功能,作用于整个构建过程,可以简单理解为webpack不能做的事情plugin都可以做
CommonsChunkPlugin
CleanWebpackPlugin
ExractTextWebpackPlugin
CopyWebpackPlugin
HtmlWebpackPlugin
UglifyjsWebpackPlugin
ZipWebpackPlugin
mode: production、development、none
module:在webpack里一切皆模块,一个模块(css、font、image)对应着一个文件,webpack会从配置的entry开始递归找出所有依赖的模块
chunk:代码块,一个chunk由多个模块组合而成,用于代码合并与分割
3 get started
npm i webpack webpack-cli webpack-dev-server(自动打包、监视文件改变、刷新浏览器) -D
创建和配置webpack.config.js(可以修改名字)
配置npm scripts
Copy "scripts": {
//--open --hot --inline --port --config ./webpack.dev.config.js
"build": "webpack --config webpack.config.js",
"dev": "webpack-dev-server --open --color --port 9999",
"dev-simple": "webpack-dev-server",
"watch": "webpack --watch"
}
安装plugin和loader
Copy npm i html-webpack-plugin clean-webpack-plugin -D
npm i babel-loader babel-core babel-preset-env (babel-loader)
npm i babel-polyfill --D (默认只转换语法,这个转换API )
npm i babel-plugin-transform-runtime --D (解决重复引用工具方法导致打包js过大的问题)
npm i babel-runtime --save (解决重复引用工具方法导致打包js过大的问题)
npm i -D style-loader css-loader (css-loader)
npm i -D npm i file-loader url-loader (image-loader)
npm i -D less-loader less(less-loader)
npm i -D vue-loader vue-template-compiler(vue-loader)
4 webpack基础
1 resolve es6
npm i @babel/core @babel/preset-env babel-loader -D
Copy //.babelrc
{
"presets": [
"@babel/preset-env"
]
}
Copy //webpack.config.js
module: {
rules: [
{
test: /.js$/,
use: 'babel-loader'
}
]
}
2 resolve jsx
npm i @babel/core babel-loader react react-dom @babel/preset-react -D
"@babel/preset-react"
in .babelrc
webpack.config.js
配置babel-loader
3 resolve css and less(sass)
npm i style-loader css-loader less less-loader -D`
Copy module: {
rules: [
{
test: /.css$/,
//注意顺序
use: ['style-loader','css-loader']
}
]
}
4 resolve less(sass)
npm i less less-loader -D
Copy module: {
rules: [
{
test: /.less$/,
use: ['style-loader','css-loader','less-loader']
}
]
}
5 file-loader(resolve img and font)
Copy module: {
rules: [
{
test: /.(png|jpg|gif|jpeg)$/,
use: 'file-loader'
},
{
test: /.(woff|woff2|eot|ttf|otf)$/,
use: 'file-loader'
}
]
}
6 url-loader
Copy //打包图片和字体,区别file-loader就是对小于一定体积的文件直接转成base64
module: {
rules: [
{
test: /.(woff|woff2|eot|ttf|otf)$/,
use: {
loader: 'url-loader',
options: {
limit:10240
}
}
}
]
}
7 文件监听
package.json
启动webpack
命令带上--watch
(需要手动刷新浏览器)
在配置webpack.config.js
中设置watch: true
Copy module.export = {
watch: true, //默认不开启
//只有开启watch,watchOptions才有意义
watchOptions: {
ignored: /node_modules/,//支持正则
aggregateTimeout: 300,//监听到变化后等300ms再去执行,越大越好
poll: 1000 //判断文件是否发生变化,不停询问系统指定文件是否有变化,默认每秒询问1000次,越小越好
}
}
8 热更新
webpack-dev-server(只能hot组件和css)
使用HotModuleReplacementPlugin插件
Copy //package.json
"scripts": {
"dev": "webpack-dev-server --open"
}
//webpack.config.js
const webpack = require('webpack')
module.export = {
...
mode: 'development',
...
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: './dist',
hot: true
//publicPath: '/assets/'
//host: '0.0.0.0',
//port: 9000,
//compress: true,
//open: true
//overlay: true,// 如果代码出错,会在浏览器页面弹出“浮动层”类似于vue-cli等脚手架
proxy: {
'/comments': {
target: 'https://xxx.cn',
changeOrigin: true,
logLevel: 'debug',
headers: {
Cookie: ''
}
}
}
//historyApiFallback
}
}
webpack-dev-middleware
适用于灵活定制的场景
Copy const express = require('express')
const wepback = require('wepback')
const weppackDevMiddleware = require('weppack-dev-middleware')
const app = expess()
const config = require('./webpack.config.js')
const compile = wepback(config)
app.use(weppackDevMiddleware(compile,{
publicPath:config.output.publicPath
}))
app.listen(3000,() => {
console.log('server is running on 3000')
})
9 文件指纹(不能和热更新一起使用)
Hash: 和整个项目构建相关,只要项目文件有修改,整个项目构建的hash就会改变
Chunkhash: 和webpack打包的chunk有关,不同entry生成不同的chunkhash
Contenthash: 根据文件内容定义hash,文件内容不变则Contenthash不变,一般css用这种
Copy const path = require('path')
//抽取css文件,webpack4的plugin,支持css chunk
//与style-loader功能互斥,不能一起使用
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname,'dist'),
//js文件指纹
filename: '[name]_[chunkhash:8].js'
//publicPath : 'dist/js/' 处理图片路径
},
mode: 'production',
module: {
rules: [
{
test: /.js$/,
use: 'babel-loader'
},
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
},
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'file-loader',
options: {
//文件指纹,此hash不同于js的hash,针对的是内容的hash
name: '[name]_[hash:8].[ext]'
}
}
]
},
{
test: /.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
//文件指纹,此hash不同于js的hash,针对的是内容的hash
name: '[name]_[hash:8].[ext]'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
//css文件指纹
filename: '[name]_[contenthash:8].css'
})
]
}
10 compress html/css/js
webpack4内置uglifyjs-weabpack-plugin
optimize-css-assets-webpack-plugin
+ cssnano
压缩CSS文件
Copy plugin: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano')
})
]
html-webpack-plugin
Copy //一个页面对应一个HtmlWebpackPlugin
new HtmlWebpackPlugin({
template: path.join(__dirname,'src/index.html'),
filename: 'index.html',
chunks: ['index'],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
11 vue-loader
Copy //preserveWhitespace 减少文件体积
{
vue: {
preserveWhitespace: false
}
}
//transformToRequire
<template>
<div>
<avatar :default-src="DEFAULT_AVATAR"></avatar>
</div>
</template>
<script>
export default {
created () {
this.DEFAULT_AVATAR = require('./assets/default-avatar.png')
}
}
</script>
//通过配置 transformToRequire 后,vue-loader会把对应的属性自动 require 之后传给组件
{
vue: {
transformToRequire: {
avatar: ['default-src']
}
}
}
<template>
<div>
<avatar default-src="./assets/default-avatar.png"></avatar>
</div>
</template>
12 webpack-chunk-name合并包
Copy const A1 = () => import(/* webpackChunkName: "A" */ '@/views/A1')
const A2 = () => import(/* webpackChunkName: "A" */ '@/views/A2')
const A3 = () => import(/* webpackChunkName: "A" */ '@/views/A3')
13 alias
Copy //vue.config.js or webpack.config.js
resolve: {
extensions: ['.js', '.vue'],
alias: {
'@': resolve('src'),
'img': resolve('src/assets/img'),
'css': resolve('src/assets/css')
}
}
//test.vue
<template>
<div class="avatar">
<img class="avatar-img" src="~img/avatar.png" alt="">
</div>
</template>
<script>
export default {
name: "Home"
}
</script>
<style scoped lang="stylus">
@import "~css/avatar";
</style>
14 公共库放到CDN( webpack-cdn-plugin
)
Copy //1 配置
externals: {
vue:'Vue',
'vue-router':'VueRouter',
vuex:'Vuex',
'element-ui':'ELEMENT',
axios: 'axios'
}
//2 对应的引用库注释掉
// import ElementUI from 'element-ui'
// import { Button, Input, Form, FormItem, Message } from 'element-ui'
// import 'element-ui/lib/theme-chalk/index.css'
// Vue.use(ElementUI)
//3 卸载依赖的`npm`包,`npm uninstall axios element-ui vue vue-router vuex`
Copy <!-- 4 项目首页引入CDN,并对CDN失效做兜底 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<!--<script src="https://cdn.bootcss.com/vue/2.5.17/vue.js"></script>-->
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script>!window.Vue && document.write(unescape('%3Cscript src="/static/cdn/vue.min.js"%3E%3C/script%3E'))</script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>!window.axios && document.write(unescape('%3Cscript src="/static/cdn/axios.min.js"%3E%3C/script%3E'))</script>
<script src="https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.min.js"></script>
<script>!window.VueRouter && document.write(unescape('%3Cscript src="/static/cdn/vue-router.min.js"%3E%3C/script%3E'))</script>
<script src="https://cdn.jsdelivr.net/npm/vuex/dist/vuex.min.js"></script>
<script>!window.Vuex && document.write(unescape('%3Cscript src="/static/cdn/vuex.min.js"%3E%3C/script%3E'))</script>
<script src="https://cdn.jsdelivr.net/npm/vue-i18n/dist/vue-i18n.min.js"></script>
<script>!window.VueI18n && document.write(unescape('%3Cscript src="/static/cdn/vue-i18n.min.js"%3E%3C/script%3E'))</script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/element-ui/lib/umd/locale/zh-CN.js"></script>
<script>!window.Element && document.write(unescape('%3Cscript src="/static/cdn/element.min.js"%3E%3C/script%3E'))</script>
<script>!window.Element && document.write(unescape('%3Cscript src="/static/cdn/element-zh.min.js"%3E%3C/script%3E'))</script>
5 webpack进阶
1 clear dist folder
通过npm scripts rm -rf./dist && webpack
or rimraf ./dist && webpack
clean-webpack-plugin
Copy plugin: [
new CleanWebpackPlugin()
]
2 PostCSS插件autoprefixer
Trident(-ms)、Geko(-moz)、Webkit(-webkit)、Presto(-o)
npm i postcss-loader autoprefixer -D
Copy {
test: /.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['last 2 version','>1%','ios 7']
})
]
}
}
]
}
3 px convert to rem
@media
Copy @media screen and (max-width:980px) {
.header {
width:900px;
}
}
npm i lib-flexible -S
(动态计算根元素font-size,打开页面时就计算)
font-size: 12px; /*no*/
不进行rem转换
Copy {
test: /.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['last 2 version','>1%','ios 7']
})
]
}
},
{
loader: 'px2rem-loader',
options: {
remUnit: 75, //1 rem = 75px
remPrecision: 8 //小数位数
}
}
]
}
4 资源内联
html/js内联(raw-loader)
Copy <!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<!-- HtmlWebpackPlugin默认使用ejs语法 -->
${ require('raw-loader!./meta.html')}
<title>Document</title>
<script>${ require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}</script>
</head>
<body>
<div id="root"><!--HTML_PLACEHOLDER--></div>
<!--INITIAL_DATA_PLACEHOLDER-->
</body>
</html>
css内联
html-inline-css-webpack-plugin
(推荐)
style-loader
Copy module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
options: {
insertAt: 'top',//插入head
singleton: true //合并所有style tag
}
},
'css-loader',
'scss-loader'
]
}
]
}
}
5 MPA bundle
Copy //不通用方案
entry: {
index: './src/index.js',
a: './src/a.js'
}
output: {
filename: '[name].[hash:8].js',
}
plugins: [
//./src/index.html需要存在
new HtmlWebpackPlugin({
filename: 'a.html',//default filename is index
template: './src/index.html',
title: 'test webpack',
hash: true,
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true
},
chunks: ['index']
}),
new HtmlWebpackPlugin({
filename: 'b.html',
template: './src/index.html',
title: 'test webpack',
hash: true,
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true
},
chunks: ['a']
})
]
Copy //通用方案
//约定每个页面是一个文件夹,入口为index.js,模版文件为index.html
// npm i glob -D
const glob = require('glob')
const path = require('path')
//动态获取entry和htmlWebpackPlugins,遵循目录结构
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname,'./src/*/index.js'));
Object.keys(entryFiles)
.map(index => {
const entryFile = entryFiles[index];
// /Users/cpselvis/my-project/src/index/index.js
const match = entryFile.match(/src\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname,`src/${pageName}/index.html`),
filename: `${pageName}.html`,
//页面上引用需要加chunk的name
chunks: ['vendors', pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
});
return {
entry,
htmlWebpackPlugins
}
}
const { entry,htmlWebpackPlugins } = setMPA()
module.exports = {
entry: entry,
output: {
path: path.join(__dirname,'dist'),
filename: '[name]_[chunkhash:8].js'
},
mode: 'production',
module: {
...
},
plugins: [
...
new CleanWebpackPlugin()
].concat(htmlWebpackPlugins)
};
6 source map(开发环境启用,线上关闭)
inline: 将.map作为DataURI嵌入,不单独生成.map
module: 包含loader的source map
Copy module.exports={
mode: 'none', //JS不会压缩
devtool: 'cheap-module-eval-source-map'
//开发环境推荐: cheap-module-eval-source-map
//生产环境推荐: cheap-module-source-map
}
7 提取公共资源
html-webpack-externals-plugin
Copy //手动html引入CDN
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
plugins: [
...
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'react',
entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js',
global: 'React',
},
{
module: 'react-dom',
entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js',
global: 'ReactDOM',
},
]
})
]
SplitChunksPlugin(代替webpack3的CommonsChunkPlugin)
Copy //分离vue、react基础包
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /(react|react-dom)/,
name: 'vendors',
chunks: 'all' //async / initial / all
}
}
}
}
}
Copy //分离页面公共文件
module.exports = {
optimization: {
splitChunks: {
minSize: 0, //分离的包体积大小
cacheGroups: {
commons: {
name: 'common',
chunks: 'all',//async initial all
minChunks: 2 // 最小引用次数
}
}
}
}
}
8 tree shaking(静态分析)
DCE(代码不可到达,代码执行结果不会被用到,代码只影响死变量(只写不读))
原理
import binding是immutable的
9 Scope Hoisting
导致问题:大量作用域包裹代码体积变大,运行代码时创建的函数作用域变多,内存开销变大
原理:将所有模块的代码按照所引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
webpack3需要使用new webpack.optimize.ModuleConcatenationPlugin()
10 code split and dynamic import
1 意义
2 方式
ES6: 动态import(目前没有原生支持,需要babel转换)
Copy //npm i @babel/plugin-syntax-dynamic-import -D
//.babelrc
{
"plugins": ["@babel/plugin-syntax-dynamic-import"],
...
}
11 webpack integrate eslint
eslint-config-airbnb、eslint-config-airbnb-base
integrate with webpack
Copy //npm i eslint-loader -D
module.exports = {
module: {
rules: [
test: /\.js$/,
exclude: /node_modules/,
use: [
"babel-loader",
"eslint-loader"
]
]
}
}
//.eslintrc.js
// npm i babel-eslint -D
module.exports = {
parser: "babel-eslint",
"extends": "airbnb",
"env": {
"browser": true,
"node",true
},
"rules": {
"semi": "error",
//0 1 2
"indent": ["error",4]
}
}
precommit hook
Copy //package.json
//npm i husky - D
"script": {
"precommit": "lint-staged"
},
"lint-stage": {
"linters": {
"*.{js.scss}": ["eslint --fix","git add"]
}
}
12 webpack打包库和基础组建
Copy //webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
entry: {
'large-number': './src/index.js',
'large-number.min': './src/index.js'
},
output: {
filename: '[name].js',
library: 'largeNumber',
libraryTarget: 'umd',
libraryExport: 'default'
},
mode: 'none',
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
include: /\.min\.js$/,
})
]
}
}
//package.json
{
"name": "large-number",
"version": "1.0.1",
"description": "大整数加法打包",
"main": "index.js",
...
}
//index.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/large-number.min.js');
} else {
module.exports = require('./dist/large-number.js');
}
13 SSR
思路
服务端:使用react-dom/server的renderToString将react组件渲染成字符串,服务端路由返回对应的模版
常见问题
没有window
、document
, 需要hack
没有fetch
、ajax
,改成axios
或isomorphic-fetch
无法解析css
服务端通过ignore-loader
忽略css解析
style-loader
换成isomorphic-css-loader
推荐 (使用浏览器端的html,设置占位符,动态插入组件和data)
Copy <!DOCTYPE html>
<html lang="en">
<head>
${ require('raw-loader!./meta.html')}
<title>Document</title>
<script>${ require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}</script>
</head>
<body>
<div id="root"><!--HTML_PLACEHOLDER--></div>
<script type="text/javascript" src="https://11.url.cn/now/lib/16.2.0/react.min.js"></script>
<script type="text/javascript" src="https://11.url.cn/now/lib/16.2.0/react-dom.min.js"></script>
<!--INITIAL_DATA_PLACEHOLDER-->
</body>
</html>
Copy //webpack.ssr.js
...
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname,'./src/*/index-server.js'));
Object.keys(entryFiles)
.map((index) => {
const entryFile = entryFiles[index];
// '/Users/cpselvis/my-project/src/index/index.js'
const match = entryFile.match(/src\/(.*)\/index-server\.js/);
const pageName = match && match[1];
if (pageName) {
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
inlineSource: '.css$',
template: path.join(__dirname,`src/${pageName}/index.html`),
filename: `${pageName}.html`,
chunks: ['vendors',pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
}
});
return {
entry,
htmlWebpackPlugins
}
}
...
//index-server.js
'use strict';
// import React from 'react';
// import largeNumber from 'large-number';
// import logo from './images/logo.png';
// import './search.less';
const React = require('react');
const largeNumber = require('large-number');
const logo = require('./images/logo.png');
require('./search.less');
class Search extends React.Component {
constructor() {
super(...arguments);
this.state = {
Text: null
};
}
loadComponent() {
import('./text.js').then((Text) => {
this.setState({
Text: Text.default
});
});
}
render() {
const { Text } = this.state;
const addResult = largeNumber('999','1');
return <div className="search-text">
{
Text ? <Text /> : null
}
{ addResult }
搜索文字的内容<img src={ logo } onClick={ this.loadComponent.bind(this) } />
</div>;
}
}
module.exports = <Search />;
//server/index.js
if (typeof window === 'undefined') {
global.window = {};
}
const fs = require('fs');
const path = require('path');
const express = require('express');
const { renderToString } = require('react-dom/server');
const SSR = require('../dist/search-server');
const template = fs.readFileSync(path.join(__dirname,'../dist/search.html'),'utf-8');
const data = require('./data.json');
const server = (port) => {
const app = express();
app.use(express.static('dist'));
app.get('/search',(req,res) => {
const html = renderMarkup(renderToString(SSR));
res.status(200).send(html);
});
app.listen(port,() => {
console.log('Server is running on port:' + port);
});
};
server(process.env.PORT || 3000);
const renderMarkup = (str) => {
const dataStr = JSON.stringify(data);
return template.replace('<!--HTML_PLACEHOLDER-->',str)
.replace('<!--INITIAL_DATA_PLACEHOLDER-->',`<script>window.__initial_data=${dataStr}</script>`);
}
14 构建输出日志
stats
: 'error-only' //none/mininal/normal/verbose,开发环境设置到devServer里
friendly-errors-webpack-plugin(plugins: [new FriendlyErrorsWebpackPlugin()]
) + stats: 'error-only'
15 构建异常处理
Copy //webpack.prod.js
plugin: [
...
function() {
//this.hooks.done.tap in webpack3
this.plugin('done', (stats) => {
if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') == -1)
{
console.log('build error');
process.exit(1);
}
})
}
]
16 others
ProvidePlugin
or expose-loader
Copy //=>内联加载器
import jquery from 'expose-loader?$!jquery';
console.log(window.$);
{
//=>只要引入JQUERY就在全局注入$
test: require.resolve('jquery'),
use: ['expose-loader?$']
}
Copy let webpack = require('webpack');
module.exports = {
plugins: [
//=>在每个模块中都注入$
new webpack.ProvidePlugin({
'$': 'jquery'
})
],
}
//=>页面中
console.log($);
DefinePlugin
//配置全局变量
Copy new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
}
})
//source code
if(process.env.NODE_ENV === 'production'){
...
}else{
...
}
webpack-manifest-plugin
&& assets-webpack-plugin
6 some plugins examples
delete useless css(webpack3)
Copy //npm i purifycss-webpack purify-css glob -D
const PurifycssWebpack = require('purifycss-webpack')
const glob = require('glob')
plugins: [
//必须绝对路径,一定放到HtmlWebpackPlugin之后
new PurifycssWebpack({
paths: glob.sync(path.resolve(src/*.html))
})
]
抽离style from js to css link文件
Copy //同mini-css-extract-plugin
//webpack3,不支持css chunk
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
module: {
rules: [
{
test: /\.css$/,
//顺序从右往左
use: ExtractTextWebpackPlugin.extract({
use: [
{loader: 'css-loader',options:{}}
]
})
},
{
test: /\.less$/,
use: ExtractTextWebpackPlugin.extract({
use: [
{loader: 'css-loader'},
{loader: 'less-loader'}
]
})
}
],
plugins: [
new ExtractTextWebpackPlugin({
filename: 'css/index.css'
})
]
}
Copy const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const lessExtract = new ExtractTextWebpackPlugin('css/less.css')
const cssExtract = new ExtractTextWebpackPlugin('css/css.css')
module: {
rules: [
{
test: /\.css$/,
use: cssExtract.extract({
use: [
{loader: 'css-loader'}
]
})
},
{
test: /\.less$/,
use: lessExtract.extract({
use: [
{loader: 'css-loader'},
{loader: 'less-loader'}
]
})
}
],
plugins: [
lessExtract,
cssExtract
]
}
Copy const lessExtract = new ExtractTextWebpackPlugin({
filename: 'css/less.css',
disable: true
})
use: cssExtract.extract({
fallback: 'style-loader',
use: [
{loader: 'css-loader',options:{}}
]
})
Copy const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins: [
new CopyWebpackPlugin([
{
from: './src/doc',
to: 'public'
}
])
]
Copy //npm i html-withimg-loader -D
<div class="img-container "><img src="./images/logo.png" alt="logo.png"></div>
{
test:/\.(html|html)$/,
use:'html-withimg-loader',
include:path.join(__dirname,'./src'),
exclude:/node_modules/
}
7 魔法注释
Copy document.addEventListener('click',function() {
import(/* webpackChunkName: 'use-lodash'*/ 'lodash')
.then(function(_) {
console.log(_.join(['3','4']))
})
})
document.addEventListener('click',() => {
import(/* webpackPrefetch: true */ './click.js')
.then(({ default: func }) => {
func()
})
})
//webpack.config.js
plugins: [
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'vendor',/* filename= */'vendor.js')
]
8 构建配置包设计
冒烟测试(是否生成html、css、js)(mocha
)
Git commit规范
Copy # npm i husky -D
# package.json
"scripts": {
"commitmsg": "validate-commit-msg",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
},
"devDependencies": {
"validate-commit-msg": "^2.11.1",
"conventional-changelog-cli": "^1.2.0",
"husky": "^0.13.1"
}
9 打包优化
分析
stats
Copy # package.json
"scriptd": {
"build:stats": "webpack --config webpack.prod.js --json > stats.json"
}
speed-measure-webpack-plugin
Copy const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
const smp = new SpeedMeasurePlugin()
const webpackConfig = smp.wrap({
plugins: [
new MyPlugin(),
new MyOtherPlugin()
]
})
优化
速度优化
多进程/多实例打包
thread-loader
(thread-loader
不可以和 mini-css-extract-plugin
结合使用)
进一步分包(预编译资源模块)
Copy //区别于html-webpack-external-plugin,只会打成一个js
//webpack.dll.js
//使用DllPlugin分包,DllReferencePlugin引用指定目录生成的manifest.json
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
library: [
'react',
'react-dom'
]
},
output: {
filename: '[name]_[chunkhash].dll.js',
path: path.join(__dirname, 'build/library'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',
path: path.join(__dirname, 'build/library/[name].json')
})
]
};
//webpack.prod,js
plugins: [
...
new webpack.DllReferencePlugin({
manifest: require('./build/library/library.json')}),
...
]
hard-source-webpack-plugin
缩小构建目标
Copy module.exports = {
...
module: {
rules: [
{
test: /.js$/,
include: path.resolve('src'),
//exclude: /(node_modules|bower_components)/
use: ['babel-loader']
}
]
},
resolve: {
alias: {
'react': path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom/umd/react-dom.production.min.js'),
'Utilities': path.resolve(__dirname,'src/utilities/'),
'Templates': path.resolve(__dirname,'src/templates/'),
'@': resolve('src'),
//配置别名将库指向同一个版本
'moment$': path.resolve('node_modules/moment/moment')
},
//减少模块层级搜索
modules: [path.resolve(__dirname, 'node_modules')],
//只查找js文件解析
//优先的放到最前面
//在源码中导入语句时,尽可能带上后缀
extensions: ['.js'],
//指定解析的入口文件
mainFields: ['main']
}
}
体积优化
Tree Shaking
js(.babelrc
设置modules:false
, production mode
默认开启,必须ES6语法)
css
purgecss-webpack-plugin
+ mini-css-exrtract-plugin
(webpack4)
purgecss-webpack-plugin
+ extract-text-webpack-plugin
(webpack3)
Copy module.exports = {
...
module: {
rules: [
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
}
]
},
plugin: [
new MiniCssExtractPlugin({
filename:'[name]_[contenthash:8].css'
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
})
]
}
image compress
Copy // image-webpack-loader
// 通过webpack-spritesmith or sprite-webpack-plugin制作雪碧图
// 使用url-loader把小图片转换成base64嵌入到JS或CSS中,减少加载次数
module.exports = {
...
module: {
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,
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
}
}
]
}
]
}
}
others
resolve.noParse(忽略依赖库的解析)
Copy module.exports = {
//...
module: {
//noParse: (content) => /jquery|lodash/.test(content)
//react.min.js经过构建,已经是可以直接运行在浏览器的、非模块化的文件
noParse:[/jquery|chartjs/,/react\.min\.js$/]
}
}
ContextReplacementPlugin
or IgnorePlugin
Copy //moment.js for example
new webpack.ContextReplacementPlugin(
/moment[/\]locale$/,
/de|fr|hu/
)
//IgnorePlugin
new Webpack.IgnorePlugin(/.\/locale/,/moment/)
//忽略后源码需要手动引入
import 'moment/locale/zh-cn'
performance
参数可以输出文件的性能检查配置
开发环境下将devtool
设置为cheap-module-eval-source-map
速度最快加速构建.在生产环境下将其设置为cheap-module-source-map