1. 第一次打包
通过npm安装好webpack和webpack-cli后,可以开始尝试打包工作。
先写好两个js文件:
helloworld.js
let helloWorld = () =>{
console.log('hello world')
}
export default helloWorld;
index.js
import helloWorld from "./helloword";
helloWorld()
再提前配置好webpack的配置文件
webpack.config.js
const path = require('path')
module.exports = {
// 打包入口
entry: './src/index.js',
// 输出的打包文件名和路径(要用绝对路径)
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
// 打包模式
mode: 'none'
}
然后在命令行中运行npx webpack(加npx是因为npm安装时是局部安装而非全局安装),会发现打包成功:
生成了一个文件夹和文件
通过在index.html中引入该js文件,即可成功在浏览器中打印出helloworld。
2. HtmlWebpackPlugin
每次都手动修改index.html里的script src路径太麻烦了,这个plugin可以帮助我们解决这个问题。
首先本地安装这个插件
npm i html-webpack-plugin -D
在config中添加这个插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 打包入口
entry: './src/index.js',
// 输出的打包文件名和路径(要用绝对路径)
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
// 打包模式
mode: 'none',
plugins: [
new HtmlWebpackPlugin()
]
}
再次运行npx webpack,会发现dist文件中除了bundle.js还生成了一个index.html,里面已经自动引入了script标签。
当然也可以对这个插件进行一些参数配置:
plugins: [
new HtmlWebpackPlugin({
// html模板
template: './index.html',
// 输出文件名
filename: 'app.html',
// script标签位置(默认在head中)
inject: 'body'
})
]
这样的话就会生成一个app.html了,以外层的index.html作为模板。
3. clean
要想使得生成新的html后自动删除之前打包的html,给output添加clean属性即可:
// 输出的打包文件名和路径(要用绝对路径)
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
clean: true
}
4. devtool
要想在浏览器中精准定位到代码出错位置,需要将bundle.js中代码解析,config中加入这个配置项即可:
devtool: 'inline-source-map'
5. webpack-dev-server
实现实时的热更新,先通过npm安装:
npm install webpack-dev-server -D
再在config中配置:
devServer: {
static: './dist'
}
服务会默认启动在8080端口,打开网址:
dist内文件都已经被存储到内存中,点进html文件控制台会打印hello world且实时热更新。
6. 资源模块
6.1 asset/resource
之前学习的webpack只能打包js文件,通过资源模块实现加载图片等内容。
加入新配置项,实现打包png格式的图片文件,并指定生成的路径和文件名(文件名由webpack随机生成一个哈希值):
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
},
]
}
生成的路径和文件名也可以配置在output中:
// 输出的打包文件名和路径(要用绝对路径)
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
clean: true,
assetModuleFilename: 'images/[contenthash][ext]'
}
随后修改index.js,将图片插入到页面中:
import helloWorld from "./helloword";
import imgSrc from './assets/img-1.png'
helloWorld()
const img = document.createElement('img');
img.src = imgSrc;
document.body.appendChild(img)
此时就实现了将图片也打包起来,并插入到app.html中。
6.2 asset/inline
通过asset/inline打包svg文件,特点在于资源被打包后不会显示在vscode文件夹中,而是生成base64的一个链接,直接将效果呈现在浏览器中:
module: {
rules: [
{
test: /\.svg$/,
type: 'asset/inline'
},
]
}
index.js
const img2 = document.createElement('img');
img2.style.cssText = 'width:600px ; height:200px'
img2.src = svgSrc;
document.body.appendChild(img2)
6.3 asset/source
用于加载文本文件,打包后也是不会显示在文件夹中。
module: {
rules: [
{
test: /\.txt$/,
type: 'asset/source'
},
]
}
const block = document.createElement('div')
block.style.cssText = 'width: 200px; height: 200px; background: aliceblue'
block.textContent = exampleText
document.body.appendChild(block)
6.4 asset
在 asset/inline 和 asset/resource 之间自动选择。默认情况下文件大于8k就会选择resource,从而在images中创建图片,但是可以通过自己修改这个临界值使得创建方式改为inline。
module: {
rules: [
{
test: /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
// 文件大小大于4m才生成resource,否则inline
maxSize: 4 * 1024 * 1024
}
}
}
]
}
7. loader
webpack默认只能理解JavaScript和JSON文件,但实际工作中各种需求层出不穷,文件类型也多种多样,比如.vue、.ts、图片、.css等。这就需要loader增强webpack处理文件的能力。
7.1 加载CSS
必须通过loader实现打包css文件
npm install css-loader -D
新增一条rules
module: {
rules: [
{
test: /\.css$/,
use: 'css-loader'
},
]
}
写一个css
.hello {
color: lightgreen;
}
将css引入到js中并给body添加类名
import './style.css'
...
document.body.classList.add('hello')
但是运行后会发现字体并没有变色,因为还需要引入另一个loader
npm install style-loader -D
然后将rules替换成:
{
test: /\.css$/,
use: ['style-loader','css-loader']
},
注意这里的style-loader和css-loader顺序不能颠倒,因为这里的loader是链式调用,从右往左执行。
7.2 加载less/sass
和加载css是比较类似的,首先安装包
npm i less less-loader -D
写一个less文件
@color: #f9efd4;
body {
background-color: @color;
}
导入到js中后,修改config,也是不能乱调loader的顺序,打包后重启webpack-dev-server即生效
{
test: /\.(css|less)$/,
use: ['style-loader','css-loader','less-loader']
},
7.3 抽离压缩CSS
之前的css代码是直接放到html代码中的,希望可以实现抽离出去通过link导入,需要通过一个插件来实现:
npm i mini-css-extract-plugin -D
下载好后实例化到plugins中:
plugins: [
new HtmlWebpackPlugin({
// html模板
template: './index.html',
// 输出文件名
filename: 'app.html',
// script标签位置(默认在head中)
inject: 'body'
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
此时要抽离出css,则style-loader就无效了,需要一个新的loader:MiniCssExtractPlugin.loader
{
test: /\.(css|less)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
},
重新打包后会发现此时css已经是通过link标签引入
但是会发现此时被打包的css文件没有被压缩,再引入一个插件来压缩css:
npm i css-minimizer-webpack-plugin -D
注意这里不是在plugins中导入了,而是新建一个属性optimization:
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
同时还要把打包模式改成生产环境:
// 打包模式
mode: 'production'
7.4 加载字体图标
其实一样的 先写css 然后加到js里 然后配config 直接一起放上来了
@font-face {
font-family: 'iconfont';
src: url('./assets/iconfont.ttf') format('truetype');
}
.icon {
font-family: 'iconfont';
font-size: 30px;
}
const span = document.createElement('span')
span.classList.add('icon')
span.innerHTML = ''
document.body.appendChild(span)
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
},
7.5 加载csv & xml
webpack原生支持处理json类型数据,别的数据需要依赖loader处理,先安装:
npm i csv-loader xml-loader -D
写config
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /\.xml$/,
use: 'xml-loader'
},
然后在index.js中打印出数据测试即可
import Data from './assets/data.xml'
import Notes from './assets/data.csv'
console.log(Data)
console.log(Notes)
7.6 自定义json模块parser
要解析yaml,toml,json5格式数据时需用到。
npm过程略
写config时需要注意,多了一个parser属性
// 注意这要导入
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
{
test: /\.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
},
{
test: /\.yaml$/,
type: 'json',
parser: {
parse: yaml.parse
}
},
{
test: /\.json5$/,
type: 'json',
parser: {
parse: json5.parse
}
}
后续打印测试同上,略。
8. babel-loader
有的js文件也需要loader解析,比如async/await,举个例子,把之前的hellowworld文件改一下:
function getString() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise!!!')
}, 2000)
})
}
async function helloWorld() {
let string = await getString()
console.log(string) // 'promise!!!'
}
export default helloWorld;
会发现helloworld的代码原封不动地被搬到了浏览器上,如果是支持es6的浏览器倒无所谓,但是如果是不支持es6的浏览器就会出问题。 因此需要通过babel-loader将代码转换成es5的。
首先npm安装三个包
npm i babel-loader @babel/core @babel/preset-env -D
都安装好后运行打包会发现报错少了一个插件:regeneratorRuntime,因此还要安装两个包(第一个包包含了那个插件):
npm i @babel/runtime -D
npm i @babel/plugin-transform-runtime -D
然后写config
{
test: /\.js$/,
// node_modules里的代码不用转化
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
9. 代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
代码分离主要有三种方法:
- 入口起点:使用 entry 配置手动地分离代码。
- 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离chunk。
- 动态导入:通过模块的内联函数调用来分离代码。
9.1 入口起点
现在新增一个文件another-module.js
import _ from 'lodash'
console.log(_.join(['Another', 'module', 'loaded!'], ' '))
需要修改一下config,entry新增一个地址,以及output需要根据entry名给filename加上不同前缀,保证不同入口输出不同名字的文件。
// 打包入口
entry: {
index : './src/index.js',
another : './src/another-module.js'
},
// 输出的打包文件名和路径(要用绝对路径)
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, './dist'),
clean: true,
assetModuleFilename: 'images/[contenthash][ext]'
},
这种方法最大的问题是不同js如果用了同样的包,比如loadsh,axios之类,这些包会被分开打包两次,本来应该是公用的只打包一次,导致了内存的浪费
9.2 防止重复
可以更改一下entry的写法来防止上面的重复打包现象:
// 打包入口
entry: {
index: {
import: './src/index.js',
dependOn: 'shared'
},
another: {
import: './src/another-module.js',
dependOn: 'shared'
},
shared: 'lodash',
},
这样就可以实现把不同模块依赖的公共库抽离成chunk
同样的效果还可以通过optimization来实现:
optimization: {
splitChunks: {
chunks: 'all'
}
}
此时entry改回之前的形式也能实现同样的效果了:
// 打包入口
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
9.3 动态导入
import返回一个promise对象,因此可以用then方法处理调用
function getComponent() {
return import('lodash')
.then(({ default: _ }) => {
const element = document.createElement('div')
element.innerHTML = _.join(['Hello', 'webpack'], ' ')
return element
})
}
getComponent().then((element) => {
document.body.appendChild(element)
})
直接在index.js中引入该文件,页面就会显示出Hello webpack
9.4 动态导入实现懒加载
首先写一个math.js文件
export const add = (x, y) => {
return x + y
}
export const minus = (x, y) => {
return x - y
}
然后在index.js中引入,使用动态加载:
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
import('./math.js').then(({ add }) => {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
来到浏览器中我们会发现,在点击按钮之前被打包出的该模块bundle.js模块代码是不会被加载的,只有被点击后才会被加载。
9.5 prefetch & preload
给刚刚的动态加载代码加上这么一句:/* webpackPrefetch: true */
button.addEventListener('click', () => {
import(/* webpackPrefetch: true */'./math.js').then(({ add }) => {
console.log(add(4, 5))
})
})
会发现打开浏览器时包就已经被加载了,那这么说起来这种方式还不如懒加载?
其实不是的,prefetch会在网络其他内容加载完,网络空闲时才下载这个包,不会影响性能。
如果把prefetch换成preload,会发现效果和上面的懒加载是基本一样的,也是一开始不加载,点击了才加载。
button.addEventListener('click', () => {
import(/* webpackPreload: true */'./math.js').then(({ add }) => {
console.log(add(4, 5))
})
})