快速上手
npm init -y
npm install webpack webpack-cli -D
-
调整 package.json 文件,确保安装包是私有的(private),并且移除 main 入口。可防止意外发布代码
{ + "private": true, - "main": "index.js", }
-
使用webpack
① 配置webpack.config.js 或 (npx webpack --config 自定义文件名)var path = require('path'); module.exports = { mode: 'production', entry: './src/index.js', /** entry: { main: './src/index.js' } **/ output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } }
② package.json中找到script新增"bundle": "webpack"
"scripts": {
"bundle": "webpack",
}
③ npm run bundle
loader 配置
- 图片、视频、字体文件打包 url-loader
npm install url-loader -D
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
name: 'img/[name]_[hash:7].[ext]',
limit: 10000 // 小于10000的生成base64
}
}]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name]_[hash:7].[ext]',
}
}]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]',
}
}]
}
]
}
}
- css打包并添加前缀 style-loader css-loader postcss-loader autoprefixer
npm i style-loader css-loader postcss-loader autoprefixer -D
添加postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
package.json中增加browserslist选项(必须添加,不然添加不上兼容性前缀)
{
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
webpack.config.js中增加loader
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1 // 引入文件时使用
}
},
'postcss-loader'
]
}]
}
}
- less文件打包 less-loader
// 提前安装style-loader和css-loader
npm install less-loader less -D
module.exports = {
module: {
rules: [{
test: /\.less$/,
use: ['style-loader','css-loader','less-loader']
}]
}
}
- scss文件打包 sass-loader
// 提前安装style-loader和css-loader和postcss-loader和autoprefixer
npm install sass-loader node-sass webpack -D
module.exports = {
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'postcss-loader', // 经测试需放在sass-loader后
'sass-loader'
]
}]
}
}
plugins使用
- html-webpack-plugin
npm install --save-dev html-webpack-plugin
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
export.modules = {
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management',
template: 'src/index.html'
})
],
}
- clean-webpack-plugin
npm install --save-dev clean-webpack-plugin
webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
};
devtool配置
source-map
(最慢) 在js文件下多了.map.js文件
inline-source-map
【inline】.map.js文件被打包到了页面引入的js文件中
cheap-inline-source-map
【cheap】打包性能提升①不用精确到列②只管业务代码,不管引入的第三方模块代码
cheap-module-inline-source-map
【module】不仅需要检查业务代码,而且需要检查引入的第三方模块代码
eval
(最快)复杂的代码提示可能不全
最优配置方案
// 开发环境
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map'
}
// 生产环境
module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map'
}
开发环境启动
-
sourceMap
webpack.config.js
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map'
}
-
watch Mode
package.json
"scripts": {
"watch": "webpack --watch"
}
- webpack-dev-server(推荐)
npm install webpack-dev-server -D
webpack.config.js
module.exports = {
devServer: {
contentBase: './dist',
open: true,
port: 8080 // 默认8080,可更改
// proxy: {
// '/api': {
// target: 'http://localhost:3000',
// pathRewrite: {'^/api': ''}
// }
// }
}
};
package.json
"scripts": {
"start": "webpack-dev-server"
}
- webpack-dev-middleware
npm install --save-dev express webpack-dev-middleware
project 增加server.js
|- package.json
|- webpack.config.js
+ |- server.js
|- /dist
|- /src
|- index.js
|- print.js
|- /node_modules
server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
// Tell express to use the webpack-dev-middleware and use the webpack.config.js
app.use(webpackDevMiddleware(compiler, {}));
// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
package.json
"scripts": {
"server": "node server.js"
},
热更新(Hot Module Replacement)
eg: D:\学习视频\webpack\源码\02-09_hot_module_replacement
webpack.config.js
const webpack = require('webpack');
module.exports = {
devServer: {
+ hot: true,
+ hotOnly: true // 即使HMR不生效、浏览器也不自动刷新
},
plugins: [
+ new webpack.HotModuleReplacementPlugin()
]
}
index.js
if(module.hot) {
module.hot.accept('./number', () => {
document.body.removeChild(document.getElementById('number'));
number();
})
}
babel配置
安装
npm install babel-loader @babel/core -D
npm install @babel/preset-env -D
npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime @babel/runtime-corejs3 -S
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
}
.babelrc
{
"presets": [
[
"@babel/preset-env", // 智能预设,翻译最新的js语法
{
"targets": {
"browsers": [
"> 1%",
"last 2 version",
"not ie<=8"
]
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime", //翻译corejs中用到的垫片
{
/*
corejs: boolean或number,默认为false。
corejs:false 不转换垫片;
corejs:2 转换垫片,不支持实例方法
corejs:3 转换垫片,支持实例方法
*/
"corejs": "3",
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
使用@babel/plugin-transform-runtime
而不是useBuiltIns:'usage'
的原因是plugin-transform-runtime
①避免重复代码 ②防止全局污染
Tree Shaking
去除引入模块中没有引入的代码
只支持ES Module静态模块的引入(import XX from XX)
package.json
{
"name":"learn-webpack",
+ "sideEffects": [ // 不进行Tree shaking的文件
"*.css"
]
}
webpack.config.js
- mode: 'development',
- optimization: {
- usedExports: true // 被使用的文件进行打包
- }
+ mode: 'production'
development和production打包文件配置
- 把webpack.config.js拆成公共、dev、prod三份配置文件
- 安装merge插件
cnpm i webpack-merge -D
,dev和prod文件中merge公共文件 - package.json里配置打包方式
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
}
code splitting
三种方式:通过配置entry、同步加载分割、异步加载分割
1. entry
提前安装lodash插件npm i lodash -S
lodash.js
import _ from 'lodash';
window._ = _;
main.js
_.join(['hello','splitting'],',');
webpack entry配置
{
entry: {
lodash:'./src/lodash.js',
main:'./src/main.js'
}
}
2. 同步加载
webpack optimization配置
optimization: {
splitChunks: {
chunks:'all'
}
}
main.js
import _ from 'lodash';
_.join(['hello','splitting'],',');
3. 异步加载(无需任何配置,会自动进行代码拆分)
async function getComponent() {
const element = document.createElement('div');
const {default: _} = await import(/* webpackChunkName: "lodash" */ 'lodash');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
getComponent().then(component => {
document.body.appendChild(component);
});
splitChunks
webpack.config.js
optimization: {
splitChunks: {
chunks: 'all', // initial 同步 async 异步 all 同步、异步
minSize: 30000, // 超过30kb才拆分打包
// maxSize: 0, // 一般不配置
minChunks: 1, // 最少使用一次
maxAsyncRequests: 5, // 同时加载的模块数最多是5个
maxInitialRequests: 3, // 首页引入最多拆分3个代码块
automaticNameDelimiter: '~', // 组合文件之间连接符
automaticNameMaxLength: 30,
name: true, // 是否支持命名
cacheGroups: { // 添加或覆盖splitChunks中的属性
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10, // 优先级(不同文件夹间比较)
name: 'vendors' // filename:只针对initial方式,否则报错
},
default: {
minChunks: 1,
priority: -20,
reuseExistingChunk: true, // 已经打包过的文件不再重新打包
name: 'commons'
}
}
}
}
懒加载文件
function getComponent() {
return import(/* webpackChunkName: "lodash" */ 'lodash').then(({default: _}) => {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
});
}
document.addEventListener('click', function () {
getComponent().then(element => {
document.body.appendChild(element);
});
})
注:函数写成async - await 的方式,打包时直接会把引入的文件打包,没有实现懒加载
css 打包
cnpm install -D mini-css-extract-plugin // 拆分css
cnpm install -D terser-webpack-plugin // 压缩
cnpm install -D optimize-css-assets-webpack-plugin // 压缩
webpack.prod.js
const TerserJSPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // MiniCssExtractPlugin.loader替换原有的style-loader
'css-loader',
'postcss-loader'
]
},
]
},
optimization: {
minimizer: [
new TerserJSPlugin({}),
new OptimizeCSSAssetsPlugin({})
],
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
}
},
plugins: [
new MiniCssExtractPlugin()
]
}
代码优化
1.打包分析 http://www.github.com/webpack/analyse
① 生成打包分析json文件
webpack --profile --json > stats.json
② http://webpack.github.com/analyse上传json可查看分析结果
2.控制台Run Command(Ctrl+shift+p) Show coverage 查看代码使用率
3.代码异步加载
eg: 新建click.js
function click() {
const element = document.createElement('div');
element.innerHTML = 'vivian';
document.body.appendChild(element);
}
export default click;
main.js
**webpackPrefetch:true **
可在任务完成后,带宽空闲时提前加载js
document.addEventListener('click', function () {
import(/* webpackPrefetch:true */'./click.js')
.then(({default: func}) => {
func();
});
})
浏览器缓存
去除打包时性能提示 webpack配置中增加
performance:false
为了防止更新文件后,浏览器缓存。在输出文件夹加contenthash
output: {
filename: '[name].[contenthash:7].js',
chunkFilename: '[name].[contenthash:7].js',
},
optimization:{
runtimeChunk: { // 解决老版本依赖文件生成不同hash值的办法
name: 'runtime'
},
splitChunks: { // 打包出vendors文件
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors'
}
}
}
}
shimming
const webpack = require('webpack');
plugins:[
new webpack.ProvidePlugin({
$:'jquery'
})
]
devServer
devServer: {
contentBase: './dist',
open: true,
port: 8080, // 默认8080,可更改
hot: true,
proxy: {
'/api': {
target: 'http://localhost:3000',
secure: false, // true时,接受运行在 HTTPS 上
pathRewrite: {'^/api': ''},
changeOrigin: true, // 突破有些接口对origin限制
headers:{
host:'www.baidu.com'
}
}
}
}
代理多个路径特定到同一个 target 下
module.exports = {
//...
devServer: {
proxy: [{
context: ['/auth', '/api'],
target: 'http://localhost:3000',
}]
}
};
webpack打包优化
- 更新webpack、node版本
- loader范围缩小
{
test: /\.js$/,
exclude:/node_modules/,
// include:path.resolve(__dirname,'./src'),
use: [{
loader: 'babel-loader'
}, {
loader: 'imports-loader?this=>window'
}]
}
- 插件的合理使用
eg:生成环境不使用压缩css插件、使用官方插件等; - 文件引入方式
resolve:{
extensions:['.js','.jsx'], // 省略后缀
mainFiles:['index','child'], // 省略文件名(不常用)
alias:{ // 别名
child:path.resolve(__dirname,'../src/a/b/c/child')
}
}
import child from 'child';
- 引入第三方文件打包到一个文件中
- 生成dll文件
webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendors: ['lodash'],
react: ['react', 'react-dom'],
jquery: ['jquery']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].mainfest.json')
})
]
}
- 指向生成的dll文件并挂载到html上
npm i add-asset-html-webpack-plugin -S
var path = require('path');
const fs = require('fs');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const plugins = [
new HtmlWebpackPlugin({
title: 'Output Management',
template: 'src/index.html'
}),
new CleanWebpackPlugin(),
new webpack.ProvidePlugin({
$: 'jquery'
})
];
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
if (/.*\.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlWebpackPlugin({ // 挂载到html上
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if (/.*\.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({ // 指向生成的dll文件
manifest: path.resolve(__dirname, '../dll', file)
}))
}
})
module.exports = {
plugin
}
- 控制包大小
- thread-loader,parallel-webpack,happypack多进程打包
- 合理使用sourceMap
- 结合stats分析打包结果
- 开发环境内存编译
- 开发环境无用插件剔除
多页面打包
plugins.push(new HtmlWebpackPlugin({ // 多页面配置多个htmlWebpackPlugin
template: 'src/index.html',
filename: `${item}.html`, // 输出文件名
chunks: ['vendors', item] // 文件引入的js
}))
const makePlugin = (configs) => {
const plugins = [
new CleanWebpackPlugin()
];
Object.keys(configs.entry).forEach(item => {
plugins.push(new HtmlWebpackPlugin({
template: 'src/index.html',
filename: `${item}.html`,
chunks: ['vendors', item]
}))
});
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)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
});
return plugins;
};
自定义loader
- 新建make-loader文件夹 --->
npm init -y
npm i webpack webpack-cli -D
- webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
resolveLoader: {
modules: ['node_modules', './loaders']
},
module: {
rules: [{
test: /\.js/,
use: [
{
loader: 'replaceLoader'
},
{
loader: 'replaceLoaderAsync',
options: {
name: 'vv'
}
}
]
}]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
- 自定义loader
npm i loader-untils -D
replaceLoaderAsync.js
const loaderUtils = require('loader-utils');
module.exports = function (source) {
const options = loaderUtils.getOptions(this);
const callback = this.async();
setTimeout(() => {
const result = source.replace('vivian', options.name);
callback(null, result);
}, 1000)
}
replaceLoader.js
module.exports = function (source) {
return source.replace('vv','vv2');
}
- package.json
{
"build":"webpack"
}
npm run build
自定义plugin
- 新建make-plugin文件夹 --->
npm init -y
npm i webpack webpack-cli -D
- 自定义plugin
copyright-webpack-plugin.js
class CopyRightWebpackPlugin {
apply(compiler) {
compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
console.log('complier');
})
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
compilation.assets['copyright.text'] = {
source: function () {
return 'copyright by vivian'
},
size: function () {
return 19;
}
}
cb();
})
}
}
module.exports = CopyRightWebpackPlugin
- webpack.config.js
const path = require('path');
const CopyRightWebpackPlugin = require('./plugins/copyright-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [
new CopyRightWebpackPlugin()
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
- package.json
{
"build":"webpack"
}
npm run build
配置eslint
npm i eslint eslint-loader -D
生成.eslintrc.js初始化文件
npx eslint --init
webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
'babel-loader',
{
loader: 'eslint-loader',
options: 'fix',
force: 'pre' // 最先执行
}
],
},
],
},
devServer: {
overlay: true, // 弹窗显示报错
contentBase: './dist',
open: true,
port: 8089
},
// ...
};