前言
本文整理了webpack前端资源构建工具以及静态模块打包器相关知识总结,如果对答案有不一样见解的同学欢迎评论区补充讨论,当然有问题,也欢迎在评论区指出。
参照引用尚硅谷webpack教程视频,以作记录
一、webpack简介
1、webpack 是什么?
(一)webpack 是一个前端资源构建工具。。
- 像前端的某些资源文件(es6、less、sass、vue等),都需要各种小工具进行解析、转义后才能使用,然后就用到webpack这种包含这些小工具的一个大的构建工具,对这些文件做处理。
(二)webpack是一个静态模块打包器。。
//index.js文件(模块)
import $ from 'jquery';(小模块)
import './index.less';(小模块)
$('#title').click(()=>{
$('body').css('backgroundColor','deepPink');
})
- index.js先是将jquery和less这些文件(这些文件叫做模块)引进来,然后形成一个chunk(代码块)
- 然后对chunk进行各项处理,如less编译成css,jquery编译成 js(浏览器能识别的语法),这些处理过程就叫做打包
- 打包后输出出去的文件,叫bundle.js
2、webpack五个核心概念
(一)Entry(入口)
指示webpack从哪个文件为入口开始打包
(二)Output(输出)
指示webpack打包后的资源输出到哪里去,以及如何命名
(三)Loader(转义)
让webpack能够去处理解析那些非js文件(webpack只能理解js)
(四)plugin(插件)
让webpack支持更多的功能,相当于扩展功能,像打包优化和压缩,重新定义环境中的变量等
- 打包前清除原dist文件中的内容------clean-webpack-plugin
- 可以自动打包生成html文件,并自动引入打包后的结果----html-webpack-plugin
- 等
(五)mode(模式)
- 开发模式(development):能让代码本地调试运行
- 生产模式(production):能让代码生产上线运行
3、初始化项目
初始化配置
npm init -y
进行初始化
npm install webpack webpack-cli -D 安装webpack到本地
编译运行
运行指令:
-
开发环境: webpack ./src/index.js -o ./build/built.js --mode=development webpack会以 ./src/index.js为入口文件开始打包,打包后输出到./build/built.js整体打包环境,是开发环境
-
生产环境: webpack ./src/index.js -o ./build/built.js --mode=production 打包后的资源会被压缩,比开发环境小
结论
- webpack能处理js/json资源,不能处理css/img等其他资源
- 生产环境和开发环境将ES6模块化编译成浏览器能识别的模块
- 生产环境比开发环境多一个压缩js代码。
4、打包资源
(一)打包样式资源
/*
webpack.config.js webpack的配置文件
作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
所有构建工具都是基于nodejs平台运行的~模块化默认采用commonjs。
*/
// resolve用来拼接绝对路径的方法
const { resolve } = require('path');
module.exports = {
// ----------------webpack配置
entry: './src/index.js', //入口文件
output: { // 输出
filename: 'built.js', // 输出文件名
path: resolve(__dirname, 'build') // 输出路径 __dirname nodejs的变量,代表当前文件的目录绝对路径
},
// loader的配置
module: {
rules: [
// 不同文件必须配置不同loader处理
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.less$/, // 匹配哪些文件
use: [ //使用哪些loader进行处理,use中loader的执行顺序:从后往前(先less->css->style)
'style-loader', //创建style标签,将js中的样式资源插入进去,添加到head中生效
'css-loader', //将css文件变成common模块加载js中,里面内容是样式字符串
'less-loader'
]
}
]
},
// plugins的配置
plugins: [],
// mode模式
mode: 'development', // 开发模式
// mode: 'production'
}
(二)打包html资源
/*
loader: 1. 下载 2. 使用(配置loader)
plugins: 1. 下载 2. 引入插件(构造函数) 3. 使用(调用函数)
*/
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: []
},
plugins: [
// html-webpack-plugin
// 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
// 需求:需要有结构的HTML文件
new HtmlWebpackPlugin({
// 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
template: './src/index.html'
})
],
mode: 'development'
};
(三)打包图片资源
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /.less$/,
// 要使用多个loader处理用use
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /.(jpg|png|gif)$/,
// 使用一个loader ,必须下载 url-loader file-loader
loader: 'url-loader', // 处理图片资源 问题:默认处理不了html中img图片
options: {
// 图片大小小于8kb,就会被base64处理
// 优点: 减少请求数量(减轻服务器压力)
// 缺点:图片体积会更大(文件请求速度更慢)
limit: 8 * 1024,
// 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs。
// 解析时会出问题:[object Module]
// 解决:关闭url-loader的es6模块化,使用commonjs解析
esModule: false,
// 给图片进行重命名,[hash:10]取图片的hash的前10位,[ext]取文件原来扩展名
name: '[hash:10].[ext]'
}
},
{
test: /.html$/,
// 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
loader: 'html-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
};
(四)打包其它资源
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader']
},
// 打包其他资源如:iconfont图标 (除了html/js/css资源以外的资源)
{
// 排除css/js/html资源
exclude: /.(css|js|html|less)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
};
(五)其它常用loader
脚本转换编译:
-
babel-loader : 加载ES6+ 代码后使用Babel转义为ES5后,浏览器才能解析
-
typescript-loader : 加载Typescript脚本文件
框架:
- vue-loader : 加载和转义vue组件,提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理
(六)devServer热更新
-
webpack-dev-server启动一个服务之后
-
浏览器和服务端是通过websocket进行长连接
-
webpack
监听源文件的变化,即当开发者保存文件时触发webpack
的重新编译。每次编译都会生成hash值
、已改动模块的json文件
、已改动模块代码的js文件
。编译完成后通过socket
向客户端推送当前编译的hash戳
-
客户端的
websocket
监听到有文件改动推送过来的hash戳
,会和上一次对比。一致则走缓存。不一致则通过ajax
和jsonp
向服务端获取最新资源
const { resolve } = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {}
plugins: [],
mode: 'development',
// 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
// 特点:只会在内存中编译打包,不会有任何输出
// 启动devServer指令为:npx webpack-dev-server (包需要下载)
devServer: {
contentBase: resolve(__dirname, 'build'), // 项目构建后路径
compress: true, // 启动gzip压缩
port: 3000, // 端口号
open: true // 自动打开本地默认浏览器
}
};
开发环境配置
/*
开发环境配置:能让代码运行
运行项目指令:
webpack 会将打包结果输出出去
npx webpack-dev-server 只会在内存中编译打包,没有输出
*/
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true
}
};
5、webpack 生产环境的基本配置
处理css
- 提取 css 成单独文件
mini-css-extract-plugin ,将a.css和b.css提取合并到单独文件main.js中
- css 兼容性处理 (有些样式在浏览器中无法解析)
postcss-loader 和 postcss-preset-env 插件
- 压缩 css
optimize-css-assets-webpack-plugin
处理js
-
js 语法检查
- eslint-loader eslint eslint-config-airbnb-base(语法风格) eslint-plugin-import
- 只检查自己写的源代码,第三方库不检查,需要设置exclude:/node_modules/ ,
- 自动修复检查出来的格式问题,需要设置options:{ fix:true}
-
js 兼容性处理
babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
- js 和html代码压缩
plugins: [
new HtmlwebpackPlugin({
template: './ src /index.html',
minify: { //设置minify,压缩htm1代码
collapsewhitespace: true,//移除空格
removeComments: true//移除注释
}
})
],
mode: 'production' //webpack的production模式,自带uglify.js,可以实现js代码压缩
生产环境配置
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /.css$/,
use: [...commonCssLoader]
},
{
test: /.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 在执行babel
*/
{
// 在package.json中eslintConfig --> airbnb
test: /.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
test: /.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
{
test: /.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /.html$/,
loader: 'html-loader'
},
{
exclude: /.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
};
6、webpack 性能优化
开发环境性能优化
-
优化打包构建速度
当修改css文件,连同js文件一起都重新打包了一次
-
HMR:hot module replacement 模块热替换,当一个模块变化,只会打包这一个模块
-
样式文件:可以使用HMR功能:因为style-loader内部实现了~ js文件:默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。 html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了~ (不用做HMR功能,html文件是主文件,包含所有的引用) 解决:修改entry入口,将html文件引入 entry: ['./src/js/index.js', './src/index.html'],
-
mode: 'development', devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, hot: true// 开启HMR功能--当修改了webpack配置,新配置要想生效,必须重新webpack服务---解决了样式文件 }
if (module.hot) {// 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效 module.hot.accept('./print.js', function() { // ----解决了js文件 // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。 // 会执行后面的回调函数 print(); }); }
-
-
优化代码调试
-
source-map-----能定位到代码错误的地方
-
devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, hot: true }, devtool: 'eval-source-map' //只需要加这行代码 //eval-cheap-module-souce-map
source-map: 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误) [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map source-map:外部 错误代码准确信息 和 源代码的错误位置 inline-source-map:内联----会导致文件过大,不推荐 只生成一个内联source-map 错误代码准确信息 和 源代码的错误位置 hidden-source-map:外部 错误代码错误原因,但是没有错误位置 不能追踪源代码错误,只能提示到构建后代码的错误位置 eval-source-map:内联 每一个文件都生成对应的source-map,都在eval 错误代码准确信息 和 源代码的错误位置 nosources-source-map:外部 错误代码准确信息, 但是没有任何源代码信息 cheap-source-map:外部 错误代码准确信息 和 源代码的错误位置 只能精确的行 cheap-module-source-map:外部 错误代码准确信息 和 源代码的错误位置 module会将loader的source map加入 内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快 ✔✔✔✔开发环境:速度快,调试更友好 速度快(eval>inline>cheap>...) eval-cheap-souce-map eval-source-map 调试更友好 souce-map cheap-module-souce-map cheap-souce-map 推荐的两种--> eval-source-map 调试更友好 / eval-cheap-module-souce-map 性能可以做到最好 ✔✔✔✔生产环境:源代码要不要隐藏? 调试要不要更友好 内联会让代码体积变大,所以在生产环境不用内联 nosources-source-map 全部隐藏 hidden-source-map 只隐藏源代码,会提示构建后代码错误信息 推荐的两种--> source-map / cheap-module-souce-map
-
生产环境性能优化
-
优化打包构建速度
-
oneOf
-
babel缓存
-
多进程打包
开启多进程打包。 进程启动大概为600ms,进程通信也有开销。 只有工作消耗时间比较长,才需要多进程打包
-
externals
禁止一些模块打包进来
mode: 'production', externals: { // 拒绝jQuery被打包进来 jquery: 'jQuery' }
-
dll
-
-
优化代码运行的性能
-
缓存(hash-chunkhash-contenthash)
-
tree shaking-----去除无用代码
前提:1. 必须使用ES6模块化 2. 开启production环境 作用: 减少代码体积 在package.json中配置 "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking) 问题:可能会把css / @babel/polyfill (副作用)文件干掉 "sideEffects": ["*.css", "*.less"] 这样就会保留css、less文件
-
code split
// 单入口 --- 单页面 // entry: './src/js/index.js', entry: { ---- 多页面 // 多入口:有一个入口,最终输出就有一个bundle index: './src/js/index.js', test: './src/js/test.js' },
/* 1. 可以将node_modules中代码单独打包一个chunk最终输出 2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk */ optimization: { splitChunks: { chunks: 'all' } }, mode: 'production'
//index.js /* 通过js代码,让某个文件被单独打包成一个chunk import动态导入语法:能将某个文件单独打包(即test文件单独打包) */ import(/* webpackChunkName: 'test' */'./test') .then(({ mul, count }) => { // 文件加载成功~ // eslint-disable-next-line console.log(mul(2, 5)); }) .catch(() => { // eslint-disable-next-line console.log('文件加载失败~'); });
-
懒加载/预加载
console.log('index.js文件被加载了~'); // import { mul } from './test'; document.getElementById('btn').onclick = function() { // 懒加载~:当文件需要使用时才加载~ ---- 但当使用某个模块,模块又非常大时,加载的就会慢 // 预加载 prefetch:会在使用之前,提前加载js文件,使用时读取缓存 ---- 兼容性较差 // 正常加载可以认为是并行加载(同一时间加载多个文件) // 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源 import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => { console.log(mul(4, 5)); }); };
-
PWA 渐进式网络开发应用程序(离线可访问)
在离线情况下,会从serviceWorker中和CacheStorage中获取资源
workbox --> workbox-webpack-plugin plugins: [ new WorkboxWebpackPlugin.GenerateSW({ /* 1. 帮助serviceworker快速启动 2. 删除旧的 serviceworker 生成一个 serviceworker 配置文件~ */ clientsClaim: true, skipWaiting: true }) ], // 在index.js 注册serviceWorker // 处理兼容性问题 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker .register('/service-worker.js') .then(() => { console.log('sw注册成功了~'); }) .catch(() => { console.log('sw注册失败了~'); }); }); }
-
总结
觉得写得好的,对你有帮助的,可以分享给身边人,知识越分享越多,千万不要吝啬呀
后续更新前端其它知识总结,请关注我,整理好,分享给你们,我们一起学前端