1,基本配置
2,高级配置
3,优化打包效率
4,优化产出代码
5,构建流程概述
6,babel
7,概念解读
8,热更新原理
1,commonJS只能用在服务端(标致性语言:module.exports,require,比如webpack)
2,AMD是commonJS的分支,可以用在浏览器端(标志性语言:define,require),代表库(require.js)
3,CMD是commonJS的分支,可以用在浏览器端(标志性语言:define,require),代表库(sea.js),只是比AMD优雅而已
4,UMD是commonJS+AMD结合,可以用在浏览器和服务器(标志性语言:module.exports,define,require)
浏览器能运行的文件js,html,css,png等,其中js语法由ECMA规范定义,es5的模块化需要引入AMD的require.js库,es6的模块化由es module实现。es6新增了很多新特性,所以浏览器支持es5,不支持es6。如何让浏览器解析es6?webpack
webpack有两个功能
1,相当于一个工具集,里面的babel可以使es6转为es5,loader等使less\scss文件转为css,以及vue,jsx文件等等。
2,将所有的资源打包,压缩,提升网速。
webpack5
1,package.json里webpack-dev-server命令改为:”dev":"webpack serve --config build/webpack.dev.js
2,webpack周边插件改动:const {merge} = require('webpack-merge')
3,const CleanWebpackPlugin = require('clean-webpack-plugin')
4,modules。rules中 loader: ['xxxx-loader']换成 use: ['xxx-loader']
5,filename: 'bundle.[contentHash:8].js'中,Hash由H换成h,即hash.
基本配置
1,基本使用
webpack是以js文件为entry入口,使用loaders处理各种模块,最后根据依赖关系进行打包的工具。webpack适合打包类似vue和react的单页面应用,不太适合打包多页面,因为单页面的js,css图片等资源互相依赖引入,而多页面之间可能没有紧密的联系。
从零搭建一个可以打包的项目流程如下:
1,webpack打包依赖nodeJS,需要下载node
2,需要初始化nodeJS模块:在空白目录 npm init生成package.json
3,在项目中包含index.html,index.js,index.css,app.js文件(vscode中安装live server插件以运行es module)。
在index.js中import 入index.css,app.js文件,这样各模块就有了依赖
4,安装webpack和webpack-cli:npm install webpack webpack-cli --save-dev
5,安装各种loaders:npm install babel-loader babel babel-core
6,在package.json文件中:"dev": “webpack”
7,运行npm run dev即可产生打包文件dist/bundle.js
8,index.html中引入bundle.js文件
1.1,打包命令
如果只配置“dev": "webpack",默认执行 webpack.config.js里的配置内容,如果想要执行webpack.common.js或者webpack.dev.js,webpack.prod.js内容,需要:
//package.json
"scripts":{
"dev": "webpack --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
"common": "webpack --config webpack.common.js"
}
1.2,各个loader用途
babel-loader: 将base6编译成es5便于浏览器解析,但是目前实验发现chrome浏览器支持大部分的es6代码。
//.babelrc配置babel
{
presets:['@babel/preset-env'],
plugins:[]
}
css-loader和style-loader:用来解析css文件的
url-loader和file-loader: 这是webpack4里面的loader,webpack5已经弃用了!!
postcss-loader:css样式做浏览器兼容处理
//postcss.config.js
module.exports={
plugins:[require('autoprefixer')]
}
1.2.1 图片处理
之前下载了webpack5的版本,然后用url-loader和file-loader处理图片,结果css文件中的背景图总是不显示,最后才发现,webpack5内部包含了资源模块,可以使用asset/resource处理图片资源。同理,html-loader,raw-loader(以前处理txt)。所以可以选择使用webpack内部的资源模块处理资源,也可以使用url-loader处理。如果不做type属性设置,则两者同时作用,会出错。
//选择使用webpack5内部资源模块处理,type为以下内容
module.exports = {
module:{
rules:[
{
test:/\.{jpg|png|gif|jpeg)$/i,
use: "asset/resource"
},
{
test: /\.html$/,
type: 'asset/resource',
generator: {
filename: 'static/[hash][ext][query]' // 单独指定 名字
}
},
{
test: /\.svg$/,
type: 'asset/inline' // inline 的时候不需要指定文件名
},
{
test: /\.txt$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb 指定大小
}
}
}
]
}
}
//使用url-loader时,type设置为'javascript/auto'
module.exports = {
module:{
rules:[
{
test:/\.jpg$/i,
use:{
loader:'url-loader',
options:{
limit:1024,
outputPath:'./images'
}
},
type:'javascript/auto'
}
]
}
}
1.2.2 hash的作用
每次内容有更改的时候,hash值就会改变,如果内容没更改就不会变,文件名也随着hash值改变,一旦文件名改变,就会重新访问网页,但是如果没变,网页就会有以前的缓存,就加载的更快。
1.3,plugins用途
html-webpack-plugin:将模板文件中引入生成的bundle.js,这个html作为一个新的文件放在dist中
2,配置拆分和merge
webpack打包可以拆分为开发模式,生产模式,以及公共模式。公共模式是二者都需要引用的公共部分。分别命名为webpack.common.js,webpack.dev.js,webpack.prod.js
引入webpack.common.js的方式,使用merge插件:
//webpack.dev.js或者webpack.prod.js
const smart= require('webpack-merge');
const commonConfig = require('./webpack.common.js');
module.exports = smart(commonConfig, {
//一些配置
mode:'development',
})
3,dev-server
在webpack.dev.js开发环境webpack配置中,一般会启用dev-server本地服务器,这个本地服务器由node.js构建,需要安装webpack-dev-server插件,启用后,构建的本地服务器使得浏览器能够检测本地代码的变化,并且自动刷新。
module.exports = smart(commonPlugin, {
mode:'development',
devServer: {
contentBase: path.join(__dirname,'dist'), //静态资源服务目录
port:8080,
compress:true, //
open:true, //是否刷新自动打开浏览器
hot:true,
host:localhost,
}
})
4,proxy代理
项目有时候会产生跨域请求,proxy代理可以将访问的接口代理到目标服务器,使得二者处于同一个域下,解决跨域请求问题。
module.exports = {
mode:'development',
proxy:{
'/api': 'http://localhost:3000/api' //将本地api代理到localhost:3000服务器下
}
}
5,打包覆盖
默认的,下次打包的内容如果文件名相同会覆盖,文件名不同的将被保留。如果我想下次打包的时候先把以前打包的内容清空,需要在wepack中配置:CleanWebpackPlugin()
需要下载:npm install clean-webpack-plugin --save-dev
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
高级配置
1,默认清空上一次的dist打包结果
在plugins中使用CleanWebpackPlugin(),注意:output需要配置path,否则插件不知道清除哪个文件夹
const {CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
output:{
path: path.resolve(__dirname,"dist"),
},
plugins:[
new CleanWebpackPlugin()
]
}
2,将css和js打包分离
一般js和css打包是放到一起的,但是假如css改变了,js没有改变,那么打包到一起会导致二者同时更新。webpack4.x以上使用MiniCssExtractPlugin分离css和js,它与style-loader冲突,建议放弃使用style-loader
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module:{
{ test: /\.css$/,
use:[MiniCssExtractPlugin.loader, 'css-loader']
}
},
plugins:[
new MiniCssExtractPlugin({
filename:"[name].css",
chunkFilename:"[id].css"
})
]
}
3,多入口
之前将monorepo项目转为multirepo时,将各个模块拆分为npm包,最后需要将各个模块打包结果发布,之前是在每个npm包中都配置一个webpack配置文件,然后根据自己的入口出口进行打包。假如现在想 在外部只创建一个webpack.config.js文件,然后想一次性打包所有的包,需要用到多入口。
//webpack.common.js
module.exports = {
entry:{
one: 'src/one/index.js', //入口名one
two: 'src/two/index.js'
},
output:{
path: './dist',
filename: '[name].bundle.js' //[name]表示根据入口名来定义打包文件名
},
plugins:[
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.html'),
filename:'one.html',
chunks: ['one' ] //表示只引入one入口打包的结果
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.html'),
filename:'two.html',
chunks: ['two' ] //表示只引入two入口打包的结果
}),
new MiniCssExtractPlugin({
filename:"[name]/[name].css", //将css文件放到one文件夹下
chunkFilename:"[id].css"
})
]
}
//这样配置最终one文件夹下的index.js打包的bundle名为:one.bundle.js
//two文件夹下的index.js打包的bundle名为:two.bundle.js
3.1,多入口文件分离
以上的配置两个入口文件全部会被打包到一个dist文件夹下,非常的杂乱,为了生成以下简洁的目录,可以在配置的时候加上输出文件路径:
以下配置可以将js,html,css资源放到各自的文件夹下,但是目前我不会处理image,只能全部放到公共的文件夹images下了。
module.exports = {
entry:{
one: 'src/one.js',
two: 'src/two.js',
},
output:{
filename:'[name]/[name].js' //针对js
},
plugins:[
new MiniCssExtractPlugin({
filename: '[name]/[name].css' //针对css
}),
new HtmlWebpackPlugin({
filename: 'one/one.html', //针对html
template: 'src/one/one.html'
})
]
}
4,文件压缩
在development开发者模式下,文件压不压缩无所谓,但是到了线上,为了服务器访问更快,需要将打包后的js,css等资源文件压缩。压缩是在plugins里配置
5,抽离公共代码和第三方模块(分割代码)
之前是将js和css分离,现在是将第三方模块以及公共模块和最终产出的bundle.js文件分离。
第三方模块类似jquery,lodash,antd等;公共代码是同一个文件被多次引用;主要在optimization这个属性中配置。
其中one.js引用了jquery和lodash,而two.js只引用了lodash。那么有两种配置方式:
1. 将jquery和lodash打包到一起,作为一个js文件被二者的html引用。
2,将jquery和lodash分别打包,生成两个js文件,被二者的html分别引用
//将jquery和lodash打包到一起
module.exports = {
optimization:{
splitChunks:{
cacheGroups:{
default:false,
vendors:false,
vendor:{
name:'vendor',
test:/[\\/]node_modules[\\/]/, //针对的文件夹
minSize:1024*1, //生成的文件最小是多少才分割
chunks:'initial', //同步,异步,all
minChunks:1 //被引用的最小次数
}
}
}
}
}
针对第二种情况,直接去掉vendor:{}中的name属性即可,会自动生成各个文件的js文件,并被引用他们的chunk的html分别引用:
问题:在不做任何splitChunk配置的情况下,异步加载的模块import()会和同步加载import的模块打包到一个chunk里吗?
回答:不会,异步加载(懒加载)会在进入这个方法的时候才打包。同步加载的模块直接打包到一起。
5.1 懒加载
以上第三方模块针对的是同步引入的静态资源, chunks:‘initial'。所谓懒加载就是异步引入某个资源,这个不是webpack独有的,是es的动态模块加载语法:
import('./data/tiles.json').then()
6,动态打包
webpack运行结果有三种方式:
scripts:{
"build": "webpack",
"watch": "webpack --watch",
"dev": "webpack-dev-server"
}
其中第一种方式表示,直接打包将结果放到dist文件夹中,每次更新代码都要手动执行打包命令。
其中第二种方式表示,直接打包将结果放到dist文件夹中,每次更新代码自动执行打包命令。
其中第三种方式表示,不生成dist文件夹,直接运行打包结果,每次更新代码自动刷新打包结果。
7,处理react(jsx)和vue文件
处理react(jsx)文件
1.需要下载:babel-loader,@babel/core,@babel/preset-env,@babel/preset-react
2.需要配置.babelrc文件: { "presets": ["@babel/preset-react"] }
3.需要配置webpack:
{
test: /\.jsx$/,
include: /src/,
use: {
loader: 'babel-loader',
options:{
presets: "@babel/preset-env"
}
}
}
处理vue文件
1.需要下载:vue-loader,应该还需要下载vue
2.需要配置webpack:
{
test: /\.vue$/,
include: /src/,
loader: 'vue-loader',
}
优化打包效率
1,babel-loader(优化从es6到es5)
2,ignorePlugin(避免引用没用的模块)
3,noParse(避免重复打包)
4,happyPack(多进程打包)
5,parallelUglifyPlugin(多进程压缩js)
6,自动刷新浏览器
7,热更新(不刷新浏览器都能更新)
8,DullPlugin(第三方库,打包以后不动了)
babel-loader
开启缓存(cacheDirectory),不会再次编译没有改变的es6代码;
明确范围(include和exclude二选一)
{
test: /\.js$/,
use:[ 'babel-loader?cacheDirectory'],
include: /src/
}
ignorePlugin
比如moment库,包含几十个语言,我们只要中英文,其他的库忽略不打包
//针对webpack配置
const webpack = require('webpack');
plugins:[
new webpack.IgnorePlugin( //webpack内置插件
{
resourceRegExp:/\.\/locale/,
contextRegExp:/moment/
})
]
//针对引用
import moment from 'moment';
import 'moment/locale/zh-ch' ;//引入需要的语言插件
noParse
比如react.js被打包后生成react.min.js,那么我们项目引用react的时候,react.min.js就不要再重复打包了。 但是还是引入的哦
module:{
noParse: [/react\.min\.js/,]
}
happyPack
多进程打包 。js是单线程,node.js也是单线程,webpack也是单线程。适用于大项目,打包时间长的效果明显,小项目使用多进程打包反而会变慢,因为启动进程以及进程之间通信都有开销。安装happypack依赖后配置:
const HappyPack = require('happypack');
module:{
rules:[
{
test:/\.js$/,
use:['happypack/loader?id=babel']
}
],
},
plugins:[
new HappyPack({
id:'babel',
loaders:['babel-loader?cacheDirectory']
})
]
parallelUglifyPlugin
多个进程同时压缩js
自动刷新
之前有讲到,使用watch进行更新后自动打包生成dist,但是不会启动浏览器。这里我们将watch作为配置属性开启,然后手动打开html,这样我们改动代码,不需要重新执行打包命令,浏览器就会自动刷新。
module.exports = {
watch:true
}
不过如果配置了webpack-dev-server,那这个属性就没必要配置了。
热更新
网页不刷新,新代码生效,状态不丢失。热更新需要配合webpack-dev-server使用。
首先下载依赖:webpack-dev-server。其中如果webpack版本是5,运行该依赖的webpack-cli不能是4,可以下载版本3(比如:3.3.12)
plugins:[
new webpack.HotModuleReplacementPlugin()
],
devServer:{
port:5500,
open:true,
hot: true //配置了HMR后,将hot设置为true即可实现热更新
}
babel
1,babel-polyfill,@babel/runtime,@babel/plugin-transform-runtime
概念解读
1,module,chunk,bundle概念
module(模块):所有使用import导入的都是模块,包括(js,css,png,jsx,json,vue)等等资源,但是作为模板的html不是模块
chunk:多个模块合并成的,可以理解为多个模块经过webpack打包生成的(js,css)等文件在内存中的内容。entry,import(),splitChunk可以定义chunk。
bundle:chunk还在内存中,bundle是实实在在打包的结果。一个chunk对应一个bundle。
2,webpack打包流程
1,生成options对象(合并webpack.config.js中所有参数)
2,实例化compiler对象(将options传递进去)
3,实例化compilition对象(compiler.run函数编译生成该对象)
4,分析入口文件,调用AST引擎生成AST语法树,遍历所有的依赖
5,调用loader,将各种类型模块生成js模块,同时生成AST语法树,继续遍历依赖
6,对生成所有的module,调用plugins,合并,拆分,生成chunk
7,将chunk生成对应的bundle输出
3,热更新原理
准备工作:
1,本地下载webpack-dev-server,wds通过express启动服务端
2,本地项目已经处于服务器中
3,配置wds相关options,使得打包后的bundle与浏览器能够建立websocket通讯
开始监听:
1,本地服务端代码发生改变
2,webpack监听到变化,重新编译生成新hash值,hot-update.json,hot-update.js
3,通过socket将hash发送到客户端,
4,客户端对比hash变化,如果一致直接到缓存拿,不一致通过ajax获取hot-update.json
5,hot-update.json中包含hash值,此时再通过jsonp获取hot-update.js