安装
npm install webpack webpack-cli --D
运行npm init 创建package.json
基本配置
创建webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',//打包环境
entry: './index.js', //打包入口文件
output: {
filename: 'bundle.js',//打包输出的文件
path: path.resolve(__dirname, 'dist') //打包文件放入的文件夹
}
}
创建index.js
const fn = (a, b) => {
return a + b
}
console.log(fn(4, 5))
执行打包命令
npx webpack
运行完命令以后文件目录里应该会有个dist文件夹,查看bundle.js
console.log(9);
配置打包命令
package.json文件修改scripts
"scripts": {
"bundle": "webpack"
},
运行 npm run bundle和npx webpack是一样的效果
Entry 与 Output 的基础配置
打包多入口文件
const path = require('path')
module.exports = {
entry: {
main: './src/index.js',
sub: './src/index.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
}
配置publicPath
如果将静态资源托管到cdn时,需要配置publicPath
module.exports = {
output: {
publicPath: 'https://cdn.example.com/assets/',
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
}
配置loader
简单来说loader就是处理一些文件的方案,webpack本身不知道怎么处理一些文件,但是loader知道,这样配置好loader就可以了
处理图片
安装url-loader file-loader
npm install url-loader file-loader -D
配置代码
module.exports = {
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader', //也可以使用file-loader,但是file-loader没有options.limit参数
options: {
name: '[name].[ext]', //不配置name为一段哈希字符串
outputPath: 'images/', //打包图片放入的文件夹
limit: 2048 //如果图片小于2kb会打包成一段base64地址放入bundle.js
}
}
}]
}
}
处理css
安装 css-loader style-loader
npm install css-loader style-loader -D
配置代码
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader'] //css-loader会处理各css中的引用,并最终生成一段css,style-loader将css-loader生成的css挂载到html的head中
}]
}
}
处理scss
安装sass-loader node-sass
npm install sass-loader node-sass -D
配置代码
module.exports = {
module: {
rules: [{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}]
}
}
增加css厂商前缀
比如我们添加一个css3的样式transform: rotate(45deg); 我们希望打包完以后成为-webkit-transform: rotate(45deg); 对css3的样式做一个兼容
安装
npm i -D postcss-loader autoprefixer
创建postcss.config.js
|– src
||-index.js
|– package.json
|– postcss.config.js
|– webpack.config.js
postcss.config.js代码
module.exports = {
plugins: [require('autoprefixer')]
}
package.json代码(通用browserslist配置)
"browserslist": [
"> 1%",
"last 2 versions"
]
webpack.config.js代码
module.exports = {
module: {
rules: [{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader']
}]
}
}
如果要对各种loader进行详细配置可以这么写
use: ['style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
配置plugins
配置HtmlWebpackPlugin
HtmlWebpackPlugin会在打包结束后自动生成一个html文件,并把打包生成的js文件自动引入到这个html文件中
安装
npm install --save-dev html-webpack-plugin
使用
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html' //指定的模板文件
})]
}
配置cleanWebpackPlugin
cleanWebpackPlugin会在打包时自动删除之前打包的dist文件内容
安装
npm install clean-webpack-plugin -D
使用
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [new CleanWebpackPlugin()]
}
配置SourceMap
在开发环境下,如果有些地方代码报错,打开错误信息显示的错误行数是打包完以后的js文件,配置好SourceMap以后就可以显示源代码错误的地方
配制
module.exports = {
devtool: 'source-map',
}
devtool的值为source-map时打包的速度是比较慢的,因为打包时要构建映射关系,建议使用eval-cheap-module-source-map
配置WebpackDevServer
安装
npm install --save-dev webpack-dev-server
使用
module.exports = {
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
compress: true,
port: 8001
},
}
package.json配置
"scripts": {
"bundle": "webpack",
"start": "webpack-dev-server --open"
},
运行npm start就可以开启一个web服务
也可以node自己建一个web服务
安装
npm install express webpack-dev-middleware -D
创建server.js文件,和webpack.config.js同级
server.js代码
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const config = require('./webpack.config.js')
const complier = webpack(config)
const app = express()
app.use(webpackDevMiddleware(complier, {
publicPath: config.output.publicPath
}))
app.listen(3000, () => {
console.log('server is running')
})
package.json增加启动命令
"scripts": {
"bundle": "webpack",
"start": "webpack-dev-server --open",
"server": "node server.js"
},
这样运行npm run server就启动了自己搭建的简易服务,不过这个服务远远没有devServer那么完善,只需了解这么写就好
配置Hot Module Replacement
热模块更新可以使代码有改动的时候不用我们手动刷新页面就可以看到改动过的最新内容
const webpack = require('webpack')
module.exports = {
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
compress: true,
port: 8001,
hot: 'only', //HMR配置
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}
配置babel处理es6语法
安装
npm install --save-dev babel-loader @babel/core
webpack.config.js配置
{
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
配置babel-loader是webpack和babel做通信的桥梁, babel-loader并不会帮助我们把es6翻译成es5,还需要配置babel/preset-env
安装
npm install @babel/preset-env --save-dev
跟目录创建babel.config.json
{
"presets": ["@babel/preset-env"]
}
写一段es6打包测试一下
const arr = [new Promise((resolve, reject) => {resolve(100)})]
arr.map(async (item) => {
const res = await item
console.log(res)
})
打包以后的代码,截取一段
var arr = [new Promise(function (resolve, reject) {\n resolve(100);\n})];\narr.map( /*#__PURE__*/function () {\n var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(item) {\n var res;\n return _regeneratorRuntime().wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _context.next = 2;\n return item;\n\n case 2:\n res = _context.sent;\n console.log(res);
可以看到const和箭头函数被转换成了es5的代码,但是promise并没有被转换,这就需要使用babel/transform-runtime再次做编译
安装
npm install --save-dev @babel/plugin-transform-runtime
npm i @babel/runtime-corejs3
使用
webpack.config.js
options: {
plugins: [
["@babel/transform-runtime", {
corejs: 3,
}]
]
}
index.js
import "core-js/stable";
import "regenerator-runtime/runtime";
const arr = [new Promise((resolve, reject) => {resolve(100)})]
arr.map(async (item) => {
const res = await item
console.log(res)
})
再次打包
var arr = [new (_babel_runtime_corejs3_core_js_stable_promise__WEBPACK_IMPORTED_MODULE_2___default())(function (resolve, reject) {\n resolve(100);\n})];\n\n_babel_runtime_corejs3_core_js_stable_instance_map__WEBPACK_IMPORTED_MODULE_3___default()(arr).call(arr, /*#__PURE__*/function () {\n var _ref = (0,_babel_runtime_corejs3_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__[\"default\"])( /*#__PURE__*/_babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee(item) {\n var res;\n return _babe
截取了一段打包后的代码,可以看到promise已经被成功转译了,为了转译es5看了有一个多小时官方文档才转译成功,主要是版本更新了以后老的polyfill不适用了,到处都是坑,打包成功那一刻真是泪目了
编译React
安装
npm install --save-dev @babel/preset-react
babel.config.json
{
"presets": ["@babel/preset-react"]
}
配置Tree Shaking
webpack.config.js增加代码
optimization: {
usedExports: true
}
Dev和Prod环境区分打包
安装webpack-merge
npm install webpack-merge -D
根目录创建build文件夹
|– src
|- build
||– webpack.common.js
||– webpack.dev.js
||– webpack.prod.js
webpack.dev.js
const path = require('path')
const webpack = require('webpack')
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const devConfig = {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
compress: true,
port: 8001,
hot: 'only',
},
//HtmlWebpackPlugin会在打包结束后自动生成一个html文件,并把打包生成的js文件自动引入到这个html文件中
plugins: [
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true
}
}
module.exports = merge(commonConfig, devConfig)
webpack.prod.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports = merge(commonConfig, prodConfig)
webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: {
main: './src/index.js',
},
output: {
filename: 'dist.js',
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]', //不配置name为一段哈希字符串
outputPath: 'images/', //打包图片放入的文件夹
limit: 2048 //如果图片小于2kb会打包成一段base64地址放入bundle.js
}
}
}, {
test: /\.scss$/,
use: ['style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
// modules: true, //模块化打包
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}, {
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}]
},
//HtmlWebpackPlugin会在打包结束后自动生成一个html文件,并把打包生成的js文件自动引入到这个html文件中
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(),
]
}
webpack.common.js是将dev和prod相同代码抽离出来,导出的时候用webpack-merge合并
package.json配置打包命令
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
}
配置split chunks
webpack.common.js增加代码
optimization: {
splitChunks: {
chunks: 'all',
},
}
all的意思是同步异步都会进行分包处理
index.js中使用lodash写两行代码
import _ from 'lodash';
console.log(_.join(['a', 'b', 'c']));
为了看打包效果,package.json中增加一项打包命令
"dev-build": "webpack --config ./build/webpack.dev.js"
打包完以后可以看到有一个vendors-node_modules_lodash_lodash_js.js
splitChunks有很多参数
splitChunks: {
chunks: 'all',
minSize: 20000, //表示如果第三方库大于20kb会做分割,否则不做分割
minChunks: 1,//表示被引入一次就会做分割
maxAsyncRequests: 30,//表示webpack在遇到前30chunks的时候,会做代码分割,超过30就不做
maxInitialRequests: 30,//表示入口文件进行加载时,入口文件可能引入其他库,超过30个也不分割
enforceSizeThreshold: 50000,//表示强制执行拆分的体积阈值
cacheGroups: {
defaultVendors: { //defaultVendors表示一旦发现引入的第三方库来自node_modules,就会打包放到vendors.js
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
filename: 'vendors.js'
},
default: {//表示默认的配置,如果引入自己写的js就按default的配置打包
priority: -20,
reuseExistingChunk: true,
}
}
}
css文件代码分割
安装
npm install --save-dev mini-css-extract-plugin
webpack.common.js增加配置项
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module: {
rules: [{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
}
style-loader改为MiniCssExtractPlugin.loader
plugins: [
new MiniCssExtractPlugin()
]
plugins增加MiniCssExtractPlugin
package.json
"sideEffects": ["*.css"]
意思是不对css做Tree Shaking
对css代码进行压缩
安装
npm install css-minimizer-webpack-plugin --save-dev
使用
webpack.common.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
}
webpack与浏览器缓存
有时候将打包好的代码放到服务器中,用户访问页面的时候我修改了某些代码重新打包上传到了服务器,这时候用户刷新页面(非强制刷新),由于js文件名字没有改动过,浏览器就会用缓存里的老的js代码,这时需要配置一个contenthash值来保证用户获取最新的服务器代码
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, '../dist')
},
Shimming
配置shimming,代码中不需要import引入库也可以正常使用
webpack.common.js
const webpack = require('webpack')
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
_: 'lodash'
})
]
index.js中写一段代码
const dom = $('<div>')
dom.html(_.join(['hello', 'world']), '')
$('body').append(dom)
打包完以后页面能正常显示hello,world,但是js中并没有引入 jquery和lodash,这就是shimming的作用
环境变量的使用
可以在打包的命令中向webpack传递环境变量
package.json
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.common.js",
"build": "webpack --env production --config ./build/webpack.common.js",
"dev-build": "webpack --config ./build/webpack.common.js"
}
在打包生产环境的代码时,传递一个env production的环境变脸给到webpack,webpack的配置也要修改一下
webpack.common.js
const { merge } = require('webpack-merge')
const devConfig = require('./webpack.dev.js')
const prodConfig = require('./webpack.prod.js')
module.exports = (env) => {
if(env && env.production) {
return merge(commonConfig, prodConfig)
} else {
return merge(commonConfig, devConfig)
}
}
webpack.common.js导出了一个函数,函数中接受一个env的参数,env.production就是配置在package.json中的环境变量
打包库文件
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
externals: ['lodash'], //运用第三方库,不对库做打包,使用时需要在,业务代码中引入lodash
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library',//script标签引入
libraryTarget: 'umd' //commonjs, amd都可以引入
}
}
配置PWA
PWA可以在离线的情况下用户依然能够访问应用
首先安装一个http-server启动一个本地的服务
npm install http-server -D
配置一项启动命令
"start": "http-server dist"
安装workbox-webpack-plugin
npm install workbox-webpack-plugin -D
webpack.prod.js
const { GenerateSW } = require('workbox-webpack-plugin');
plugins: [
new GenerateSW ({
clientsClaim: true,
skipWaiting: true
})
]
index.js注册service-worker
console.log('hello world')
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('service-worker registered')
}).catch(err => {
console.log('service-worker error')
})
})
}
配置完了后打包一个生产环境的代码,运行npm run start启动本地项目,然后停掉服务,页面刷新依然能够正常访问
打包TS
安装
npm install ts-loader typescript -D
根目录创建tsconfig.json
{
"compilerOptions": {
"outDir": "./dist",
"module": "es6",
"target": "es5",
"allowJs": true
}
}
webpack.config.js配置
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.ts',
module: {
rules: [{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'dist.js',
}
}
配置webpackDevServer实现开发环境请求转发
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
proxy: {
'/api': {
target: 'http://www.abc.com',
changeOrigin: true
}
},
compress: true,
port: 8001,
hot: 'only',
}
解决单页应用路由问题
路由模式为BrowserRouter,如果服务器没有对应的路由文件就会返回404,可配置historyApiFallback为true,HashRouter是前端控制的路由模式,所以不需要配置就可正常访问路由
devServer: {
...
historyApiFallback: true
},
提升webpack打包速度的方法
1.对node.npm进行升级
2.让loader的作用范围更小
3.尽可能少的使用plugin,并保证可靠性
4.resolve参数合理配置
resolve: {
extensions: ['.png', '.css', '.js', '.jsx']
}
对.png, .css这种文件不要写到extensions,webpack查找一次文件就会有相应的性能损耗
5.使用DllPlugin
核心思路就是首先将第三方库进行一次打包,打包业务代码的时候不用再去打包第三方库,直接引入打包完的第三方库即可
创建webpack.dll.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom'] //将react, react-dom进行打包
},
output: {
filename: '[name].dll.js',//打包完以后的名字
path: path.resolve(__dirname, '../dll'),//放到哪个文件夹
library: '[name]'//打包完向外暴露出去的名字
},
plugins: [
new webpack.DllPlugin({
name: '[name]',//分析dll文件
path: path.resolve(__dirname, '../dll/[name].manifest.json')//分析完生成manifest.json
})
]
}
package.json添加一项打包命令
"build:dll": "webpack --env production --config ./build/webpack.dll.js",
安装add-asset-html-webpack-plugin
npm install add-asset-html-webpack-plugin --save
在webpack.common.js中使用
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
const webpack = require('webpack')
...
plugins: [
new AddAssetHtmlWebpackPlugin({ //将文件挂载到html中
filepath: path.resolve(__dirname, '../dll/vendors.dll.js'),
publicPath: '../dll'
}),
new webpack.DllReferencePlugin({ //打包时不用再分析node_modules的相关依赖文件,直接使用打包好的vendors文件
manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
})
]
6. 控制包文件大小(通过tree shaking把无用的包去除)
7.合理配置source-map
多页面打包
entry: {
main: './src/index.js',
list: './src/list.js',
}
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'index.html',
chunks: ['main', 'vendors']
}),
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'list.html',
chunks: ['list', 'vendors']
}),
],
编写一个loader
新建一个loaders文件夹,再新建一个replaceLoader.js
module.exports = function(source) {
console.log(this.query)
return source.replace('hello', this.query.name)
}
导出对象必须是一个function,不能是箭头函数,因为里面要用到this,这个loader做了一件事情,就是把js文件中的hello替换为配置时传的字符
webpack.config.js
module: {
rules: [{
test: /\.js/,
use: [{
loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
options: {
name: 'goodby'
}
}]
}]
}
options.name就是传递给loader的参数
编写一个plugin
新建一个plugins文件夹,再新建一个copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
compilation.assets['copyright.txt'] = {
source: function() {
return 'copyright by webpack'
},
size: function() {
return 20
}
}
cb()
})
}
}
module.exports = CopyrightWebpackPlugin
plugin编写必须要是以类的方式,这也是使用plugin的时候需要new的原因,这个plugin做的事情就是在即将打包完文件的时候生成一个copyright.txt,将版权信息写入文件
webpack.config.js使用
const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin')
...
plugins: [
new CopyrightWebpackPlugin()
]