之前使用面向对象编程,页面须要引入多个js,形成多个请求,影响加载,须要注意引用的顺序,使用时没法直接从js里看出文件的层级关系,一旦出错调试很麻烦css
// /index.html
<div id="root"></div>
<script src="./header.js"></script>
<script src="./index.js"></script>
// /header.js
function Header() {
var root = document.getElementById('root')
var header = document.createElement('div')
header.innerText = 'header'
root.appendChild(header)
}
// /index.js
new Header()
复制代码
解决传统编程的弊端html
建立文件写代码:vue
// /index.html
<div id="root"></div>
// /header.js
function Header() {
var root = document.getElementById('root')
var header = document.createElement('div')
header.innerText = 'header'
root.appendChild(header)
}
export default Header
// /index.js
import Header from './header.js'
new Header()
复制代码
npx webpack index.js # 编译 index.js 文件,生成 ./dist/main.js 文件node
// /index.html 中引入编译后的文件
<script src="./dist/main.js"></script>
复制代码
export default Header // 导出
import Header from './header.js' // 引入
复制代码
module.exports = Header // 导出
var Header = require('./header.js') // 引入
复制代码
// package.json
{
"private": true, // 表示该项目是私有项目,不会被发送到 npm 的线上仓库
"main": "index.js", // 若是项目不被外部引用,则不须要向外部暴露一个 js 文件,可将该行删除
"scripts": { // 配置 npm 命令, 简化命令行输入的命令
"build": "webpack", // 不用加 npx, 会优先从项目目录中去找 webpack; 配置以后可以使用 npm run build 代替 npx webpack
}
}
复制代码
// webpack.config.js
const path = require('path') // 引入一个 node 的核心模块 path
module.exports = {
entry: './index.js', // 打包入口文件
// entry: { // 上面是该种写法的简写
// main: './index.js'
// },
output: {
filename: 'main.js', // 打包后的文件名
path: path.resolve(__dirname, 'dist') // 打包后文件的路径(要指定一个绝对路径); 经过 path 的 resolve 方法将当前路径(__dirname)和指定的文件夹名(dist)作一个拼接
},
mode: 'production', // 配置打包的模式(production/development); 生产模式(会压缩)/开发模式(不会压缩)
}
复制代码
Hash: d8f9a3dacac977cc0968 # 打包对应的惟一 hash 值
Version: webpack 4.40.2 # 打包使用的 webpack 版本
Time: 208ms # 打包消耗的时间
Built at: 2019-09-20 16:38:59 # 打包的当前时间
Asset Size Chunks Chunk Names
# 生成的文件 文件大小 文件对应id 文件对应名字
main.js 930 bytes 0 [emitted] main
Entrypoint main = main.js # 打包的入口文件
[0] ./index.js 36 bytes {0} [built] # 全部被打包的文件
WARNING in configuration # 警告: 未指定打包的模式(默认会以生产模式打包)
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
复制代码
webpack 默认知道如何打包 js 文件,loader 的做用就是告诉 webpack 如何打包其它不一样类型的文件react
使用 file-loader 打包一些图片文件(须要执行命令 npm i file-loader -D 安装 file-loader)jquery
// webpack.config.js
module.exports = {
module: {
rules: [{
// test: /\.jpg$/,
test: /\.(jpg|png|gif)$/, // 配置容许匹配多个文件类型
use: {
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]', // 配置打包后文件的名称(name:文件原名;hash:哈希值;ext:文件后缀;最终生成:文件原名_哈希值.文件后缀),若不配置,文件会以哈希值命名
outputPath: 'static/img/' // 配置打包后文件放置的路径位置
}
}
}]
}
}
复制代码
与 file-loader 相似,还可使用 url-loader 打包一些图片文件(一样须要先安装)webpack
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'static/img/',
limit: 10240 // 与 file-loader 不一样的是,能够配置 limit 参数(单位:字节),当文件大于 limit 值时,会生成独立的文件,小于 limit 值时,直接打包到 js 文件里
}
}
}]
}
}
复制代码
注:url-loader 依赖 file-loader,使用 url-loader 同时须要安装 file-loaderios
在 webpack 的配置里,loader 是有前后顺序的,loader 的执行顺序是从下到上,从右到左的git
注:node-sass没法安装时,可采用cnpm或查看node-sass没法安装时的解决办法es6
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/, // .css 结尾的文件使用 style-loader 和 css-loader 打包(须要安装 style-loader 和 css-loader)
use: ['style-loader', 'css-loader'] // css-loader 会帮咱们分析出多个 css 文件之间的关系,将多个 css 合并成一个 css;style-loader 将 css-loader 处理好的 css 挂载到页面的 head 部分
}, {
test: /\.scss$/, // .scss 结尾的文件使用 style-loader 和 css-loader 和 sass-loader 打包(须要安装 style-loader 和 css-loader 和 sass-loader 和 node-sass)
use: ['style-loader', 'css-loader', 'sass-loader'] // 这里先执行 sass-loader 将 sass 代码翻译成 css 代码;而后再由 css-loader 处理;都处理好了再由 style-loader 将代码挂载到页面上
}]
}
}
// index.js
// 配置好以后在 js 中引入 css 便可
import './index.css'
复制代码
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css/,
use: ['postcss-loader'] // 须要执行 npm i postcss-loader -D 安装 postcss-loader
}]
}
}
// postcss.config.js // 在根目录下建立该文件
module.exports = {
plugins: [
require('autoprefixer') // 须要执行 npm i -D autoprefixer 安装 autoprefixer
]
}
复制代码
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css/,
use: ['style-loader', {
loader: 'css-loader',
options: {
importLoaders: 1 // 有时候会在一个样式文件里 import 另外一个样式文件,这就须要配置 importLoaders 字段,是指在当前 loader 以后指定 n 个数量的 loader 来处理 import 进来的资源(这里是指在 css-loader 以后使用 sass-loader 来处理 import 进来的资源)
}
}, 'sass-loader']
}]
}
}
复制代码
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true // 开启 css 的模块化打包
}
}]
}]
}
}
// index.css(若使用 sass,增长对应 loader 便可)
.avatar {
width: 100px;
height: 100px;
}
// index.js
import style from './index.css'
var img = new Image()
img.src = ''
img.classList.add(style.avatar)
复制代码
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.(eot|ttf|svg)$/,
use: ['file-loader']
}]
}
}
复制代码
plugin 能够在 webpack 运行到某个时刻的时候帮你作一些事情(相似 vue 的生命周期函数同样)
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [new HtmlWebpackPlugin({
template: 'index.html' // 指定生成 html 的模版文件(若是不指定,则会生成一个默认的不附带其它内容的 html 文件)
})]
}
复制代码
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'dist')] // 若不配置,默认删除 output 下 path 指定的目录
})]
}
复制代码
// webpack.config.js
const path = require('path')
module.exports = {
// entry: './src/index.js', // 简写方式
entry: {
main: './src/index.js'
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
复制代码
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index1: './src/a.js',
index2: './src/b.js'
},
output: {
publicPath: 'http://cdn.com.cn', // 会在自动生成的 html 文件中,引入文件路径的前面加上此路径
filename: '[name].[hash].js', // name 即指 entry 中配置的须要打包文件的 key (也即 index1 和 index2, 最终会生成 index1.js 和 index2.js)
path: path.resolve(__dirname, 'dist')
},
plugins: [new HtmlWebpackPlugin()]
}
复制代码
// webpack.config.js
module.exports = {
devtool: 'source-map'
// devtool: 'cheap-module-eval-source-map' // 经常使用于开发环境
// devtool: 'cheap-module-source-map' // 经常使用于生产环境
}
复制代码
devtool | 解释 |
---|---|
none | 不生成 sourceMap |
source-map | 生成 .map 文件 |
inline-source-map | 不生成 .map 文件,sourceMap 会被合并到打包生成的文件里 |
cheap-source-map | 只告诉出错的行,不告诉出错的列 |
cheap-module-source-map | 除了业务代码里的错误,还要提示一些 loader 里面的错误 |
eval | 不生成 .map 文件,使用 eval 在打包后文件里生成对应关系 |
在 webpack 命令后面加 --watch,webpack 会监听打包的文件,只要文件发生变化,就会自动从新打包
// package.json
{
"scripts": {
"watch": "webpack --watch"
}
}
复制代码
// webpack.config.js
module.exports = {
devServer: {}
}
// package.json
{
"scripts": {
"wdserver": "webpack-dev-server"
}
}
复制代码
open: true // 启动服务的时候自动在浏览器中打开当前项目(默认 false)
port: 8888 // 自定义启动服务的端口号(默认 8080)
contentBase: './static' // 指定资源的请求路径(默认 当前路径)
例如:
/static 文件夹下存在一张图片 /static/img.png
devServer 里配置 contentBase: './static'
/index.html 中使用 <img src="img.png" />
这样它就会去 /static 文件夹下去找 img.png 而不是从根目录下去找 img.png
复制代码
借助 express 和 webpack-dev-middleware 本身手动搭建服务
// server.js(在 node 中使用 webpack)
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackConfig = require('./webpack.config.js')
const complier = webpack(webpackConfig)
const app = express()
app.use(webpackDevMiddleware(complier, {}))
app.listen(3000, () => {
console.log('server is running at port 3000')
})
复制代码
// package.json
{
"scripts": {
"nodeserver": "node server.js"
}
}
复制代码
webpack index.js -o main.js # 编译 index.js 输出 main.js
HotModuleReplacementPlugin 是 webpack 自带的一个插件,不须要单独安装
// webpack.config.js
const webpack = require('webpack')
module.exports = {
devServer: {
hot: true, // 让 webpack-dev-server 开启 hot module replacement 这样的一个功能
hotOnly: true // 即使是 hot module replacement 的功能没有生效,也不让浏览器自动刷新
},
plugins: [new webpack.HotModuleReplacementPlugin()]
}
复制代码
更改样式文件,页面就不会整个从新加载,而是只更新样式
// /index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>html 模板</title>
</head>
<body></body>
</html>
复制代码
// /src/index.css
div {
width: 100px;
height: 100px;
}
div:nth-of-type(odd) {
background-color: rgb(255, 0, 0);
}
复制代码
// /src/index.js
import './index.css'
var btn = document.createElement('button')
btn.innerText = 'button'
document.body.appendChild(btn)
btn.onclick = () => {
var item = document.createElement('div')
item.innerText = 'item'
document.body.appendChild(item)
}
复制代码
// /package.json
{
"name": "webpack-test",
"version": "1.0.0",
"description": "",
"private": false,
"scripts": {
"wdserver": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^3.2.0",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^1.0.0",
"webpack": "^4.41.1",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2"
}
}
复制代码
// /webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
entry: {
main: './src/index.js'
},
output: {
publicPath: '/',
filename: '[name].[hash].js',
path: path.resolve(__dirname, 'dist')
},
mode: 'production',
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
hot: true,
hotOnly: true
}
}
复制代码
更改 number.js 文件中的代码,只会从页面上移除 id 为 number 的元素,而后从新执行一遍 number() 方法,不会对页面上的其它部分产生影响,也不会致使整个页面的重载
// /index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>html 模板</title>
</head>
<body></body>
</html>
复制代码
// /src/counter.js
function counter() {
var div = document.createElement('div')
div.setAttribute('id', 'counter')
div.innerText = 1
div.onclick = function() {
div.innerText = parseInt(div.innerText, 10) + 1
}
document.body.appendChild(div)
}
export default counter
复制代码
// /src/number.js
function number() {
var div = document.createElement('div')
div.setAttribute('id', 'number')
div.innerText = 20
document.body.appendChild(div)
}
export default number
复制代码
// /src/index.js
import counter from './counter'
import number from './number'
counter()
number()
// 相比 css 须要本身书写重载的代码,那是由于 css-loader 内部已经帮 css 写好了这部分代码
if (module.hot) {
module.hot.accept('./number', () => {
// 监测到代码发生变化,就会执行下面的代码
document.body.removeChild(document.getElementById('number'))
number()
})
}
复制代码
// /package.json
{
"name": "webpack-test",
"version": "1.0.0",
"description": "",
"private": false,
"scripts": {
"wdserver": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.41.1",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2"
}
}
复制代码
// /webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
entry: {
main: './src/index.js'
},
output: {
publicPath: '/',
filename: '[name].[hash].js',
path: path.resolve(__dirname, 'dist')
},
mode: 'production',
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
hot: true,
hotOnly: true
}
}
复制代码
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/, // 不去匹配 node_modules 文件夹下的 js
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}]
}
}
复制代码
上面的步骤,只是作了语法上的翻译(如: let/const/箭头函数/... 都会被转换),但一些新的变量和方法并无被翻译(如: promise/.map()/...),这时就要使用 @babel/polyfill 来处理
像上面配置好以后,会发现打包后的文件特别大,由于一些没用到的 ES6 语法也被打包了进去,所以须要作以下操做
// webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
corejs: 3,
useBuiltIns: 'usage',
targets: { // 经过 targets 指定项目运行的环境,打包时会自动判断是否须要去解析转化代码
chrome: '67'
}
}
]
]
}
}]
}
}
复制代码
若是写的是业务代码,可采用上面方法使用 polyfill 去打包;若是是开发组件或者库的话,可以使用 plugin-transform-runtime polyfill 会污染全局环境,plugin-transform-runtime 会以闭包的形式帮助组件去引入相关内容 @babel/plugin-transform-runtime 官方文档
// /index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>html 模板</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
复制代码
// /src/index.js
import React, { Component } from 'react'
import ReactDom from 'react-dom'
class App extends Component {
render() {
return <div>Hello World</div>
}
}
ReactDom.render(<App />, document.getElementById('app'))
复制代码
// /.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"corejs": 3,
"useBuiltIns": "usage",
"targets": {
"chrome": 67
}
}
],
"@babel/preset-react"
]
}
复制代码
// /package.json
{
"name": "webpack-test",
"version": "1.0.0",
"description": "",
"private": false,
"scripts": {
"wdserver": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/polyfill": "^7.6.0",
"@babel/preset-env": "^7.6.3",
"@babel/preset-react": "^7.6.3",
"@babel/runtime-corejs3": "^7.6.3",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.3.2",
"html-webpack-plugin": "^3.2.0",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"webpack": "^4.41.1",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2"
}
}
复制代码
// /webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpack = require('webpack')
module.exports = {
entry: {
main: './src/index.js'
},
output: {
publicPath: './',
filename: '[name].[hash].js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'dist')]
}),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
hot: true,
hotOnly: true
}
}
复制代码
module.exports = {
optimization: {
usedExports: true
}
}
复制代码
npm i -D webpack-merge # 安装 webpack-merge 模块,做用是将公共的 webpack 配置代码与开发 / 生产环境中的 webpack 配置代码进行合并
/build/webpack.common.js # 存放公共的 webpack 配置代码
// 示例仅展现部分代码(下同)
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: {
main: './src/index.js'
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [path.resolve(process.cwd(), 'dist')] // __dirname => process.cwd()
})
],
output: {
filename: '[name].[hash].js',
path: path.resolve(__dirname, '../dist') // dist => ../dist
}
}
复制代码
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const devConfig = {
mode: 'development'
}
module.exports = merge(commonConfig, devConfig)
复制代码
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const prodConfig = {
mode: 'production'
}
module.exports = merge(commonConfig, prodConfig)
复制代码
{
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
}
}
复制代码
// /src/lodash.js
import _ from 'lodash'
window._ = _
复制代码
// /src/index.js
console.log(_.join(['a', 'b', 'c'])) // 输出a,b,c
console.log(_.join(['a', 'b', 'c'], '***')) // 输出a***b***c
复制代码
// /build/webpack.common.conf.js
module.exports = {
entry: {
lodash: './src/lodash.js',
main: './src/index.js'
}
}
复制代码
webpack 中的代码分割底层使用的是 SplitChunksPlugin 这个插件
// /src/index.js
import _ from 'lodash'
console.log(_.join(['a', 'b', 'c'])) // 输出a,b,c
console.log(_.join(['a', 'b', 'c'], '***')) // 输出a***b***c
复制代码
// /build/webpack.common.conf.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
复制代码
// /src/index.js
function getComponent() {
return import('lodash').then(({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['hello', 'world'], '-')
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
复制代码
经过 import('lodash') 引入,分割打包后的文件名称是 [id].[hash].js,打包后文件的辨识度不高; 使用 import(/* webpackChunkName: "lodash" */ 'lodash') 来为打包后的文件起别名,提高辨识度(最终生成文件名称为:vendors~lodash.[hash].js,意思是符合 vendors 组的规则,入口是main),详情可搜索查看 SplitChunksPlugin 的配置 这种方式被称为魔法注释,详情可查看魔法注释 Magic Comments 官网地址
注意: 若是报错“Support for the experimental syntax 'dynamicImport' isn't currently enabled”,可安装 @babel/plugin-syntax-dynamic-import 进行解决
@babel/plugin-syntax-dynamic-import 官网地址
// npm i -D @babel/plugin-syntax-dynamic-import # 安装模块包
// /.babelrc # 配置
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
复制代码
// /build/webpack.common.conf.js
module.exports = {
output: {
filename: '[name].[hash].js', // 入口文件根据 filename 命名
chunkFilename: '[name].chunk.js', // 非入口文件根据 chunkFilename 命名
path: path.resolve(__dirname, '../dist')
}
}
复制代码
// SplitChunksPlugin 的默认配置
// webpack.common.conf.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'async', // async:作代码分割时,只对异步代码生效;all:对同步和异步代码都生效;initial:只对同步代码生效
minSize: 30000, // 单位:字节;当打包的库大于 minSize 时才作代码分割,小于则不作代码分割
maxSize: 0, // 当打包的库大于 maxSize 时,尝试对其进行二次分割,通常不作配置
minChunks: 1, // 当一个模块被用了至少 minChunks 次时,才对其进行代码分割
maxAsyncRequests: 5, // 同时加载的模块数最可能是 maxAsyncRequests 个,若是超过 maxAsyncRequests 个,只对前 maxAsyncRequests 个类库进行代码分割,后面的就不作代码分割
maxInitialRequests: 3, // 整个网站首页(入口文件)加载的时候,入口文件引入的库进行代码分割时,最多只能分割 maxInitialRequests 个js文件
automaticNameDelimiter: '~', // 打包生成文件名称之间的链接符
name: true, // 打包起名时,让 cacheGroups 里的名字有效
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, // 若是是从 node_modules 里引入的模块,就打包到 vendors 组里
priority: -10 // 指定该组的优先级,若一个类库符合多个组的规则,就打包到优先级最高的组里
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 若是一个模块已经被打包过了(一个模块被多个文件引用),那么再打包的时候就会忽略这个模块,直接使用以前被打包过的那个模块
}
}
}
}
}
复制代码
注:SplitChunksPlugin 上面的一些配置须要配合 cacheGroups 里的配置一块儿使用才能生效(如 chunks 的配置)
// webpack.common.conf.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
filename: 'vendors.js' // 配置 filename 以后,打包会以 filename 的值为文件名,生成的文件是 vendors.js
},
default: false
}
}
}
}
复制代码
LazyLoading:懒加载并非 webpack 里面的概念,而是 ES 里面的概念;何时执行,何时才会去加载对应的模块
// /src/index.js
import _ from 'lodash'
document.addEventListener('click', () => {
var element = document.createElement('div')
element.innerHTML = _.join(['hello', 'world'], '-')
document.body.appendChild(element)
})
复制代码
// /src/index.js
function getComponent() {
return import(/* webpackChunkName: "lodash" */ 'lodash').then(
({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['hello', 'world'], '-')
return element
}
)
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
复制代码
使用 ES7 的 async 和 await 后,上面代码能够改写成下面这种写法,效果等同
// /src/index.js
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash')
const element = document.createElement('div')
element.innerHTML = _.join(['hello', 'world'], '-')
return element
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
复制代码
打包后生成的每个 js 文件,都是一个 chunk
// /package.json
{
"scripts": {
"build": "webpack --profile --json > stats.json"
}
}
复制代码
附录:
除了 webpack 官方提供的分析工具,还有不少其它的分析工具,可查看GUIDES/Code Splitting/Bundle Analysis:
改写前:
// /src/index.js
document.addEventListener('click', () => {
var element = document.createElement('div')
element.innerHTML = 'hello world'
document.body.appendChild(element)
})
复制代码
改写后:
// /src/handleClick.js
function handleClick() {
const element = document.createElement('div')
element.innerHTML = 'hello world'
document.body.appendChild(element)
}
export default handleClick
// /src/index.js
document.addEventListener('click', () => {
import('./handleClick.js').then(({ default: func }) => {
func()
})
})
复制代码
// /build/webpack.common.conf.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'async' // 默认(async)只对异步代码作分割
}
}
}
复制代码
Prefetching/Preloading modules 官网地址
/* webpackPrefetch: true */
/* webpackPreload: true */
复制代码
// /src/handleClick.js
function handleClick() {
const element = document.createElement('div')
element.innerHTML = 'hello world'
document.body.appendChild(element)
}
export default handleClick
// /src/index.js
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */ './handleClick.js').then(({ default: func }) => {
func()
})
})
复制代码
npm install --save-dev mini-css-extract-plugin
// /build/webpack.common.conf.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({})
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // 使用了 MiniCssExtractPlugin.loader 就不须要 style-loader 了
'css-loader'
]
}
]
}
}
复制代码
注意: 若是使用了 TreeShaking (排除未使用的代码)还需配置
// /package.json
{
"sideEffects": ["*.css"]
}
复制代码
// /package.json
{
"scripts": {
"build": "webpack --config ./build/webpack.prod.conf.js"
}
}
复制代码
npm run build
// /build/webpack.common.conf.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css', // 打包后的 css 若是被页面直接引用,就以 filename 的规则命名
chunkFilename: '[name].chunk.css' // 打包后的 css 若是是间接引用的,就以 chunkFilename 的规则命名
})
]
}
复制代码
// /build/webpack.prod.conf.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
}
复制代码
// /src/index.js
import './index1.css'
import './index2.css'
复制代码
// /build/webpack.prod.conf.js
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles', // 打包后的文件名
test: /\.css$/, // 匹配全部 .css 结尾的文件,将其放到该组进行打包
chunks: 'all', // 无论是同步仍是异步加载的,都打包到该组
enforce: true // 忽略默认的一些参数(好比minSize/maxSize/...)
}
}
}
}
}
复制代码
根据入口文件的不一样,将 css 文件打包到不一样的文件里 参考Extracting CSS based on entry 官网地址
// /build/webpack.prod.conf.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
}
复制代码
注意:
对于老版本的 webpack,即使没有对源代码作任何的变动,有可能两次打包的 contenthash 值也不同,这是由于打包生成的文件之间存在关联,这些关联代码叫作 manifest,存在于各个文件中,可经过额外的配置,将关联代码提取出来
// /build/webpack.common.conf.js
module.exports = {
optimization: {
runtimeChunk: {
name: 'runtime' // 打包后会多出一个 runtime.js 用于存储文件之间的关联代码
}
}
}
复制代码
// /build/webpack.common.conf.js
module.exports = {
performance: false
}
复制代码
// /src/jquery.ui.js
export function ui() {
$('body').css('background-color', _.join(['green'], ''))
}
复制代码
// /src/index.js
import { ui } from './jquery.ui'
ui()
复制代码
// /build/webpack.common.conf.js
const webpack = require('webpack')
module.exports = {
plugins: [new webpack.ProvidePlugin({
$: 'jquery',
_: 'lodash',
_join: ['lodash', 'join'] // 若是想直接使用 _join 替代 lodash 的 join 方法,能够这样配置
})]
}
复制代码
// /build/webpack.common.conf.js
module.exports = {
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader'
}, {
loader: 'imports-loader?this=>window'
}]
}]
}
}
复制代码
以上这些更改 webpack 打包的一些默认行为,或者说实现一些 webpack 原始打包实现不了的效果,的行为都叫作 Shimming (垫片的行为)
// /build/webpack.prod.conf.js
const prodConfig = {
// ...
}
module.exports = prodConfig
复制代码
// /build/webpack.dev.conf.js
const devConfig = {
// ...
}
module.exports = devConfig
复制代码
// /build/webpack.common.conf.js
const merge = require('webpack-merge')
const devConfig = require('./webpack.dev.conf.js')
const prodConfig = require('./webpack.prod.conf.js')
const commonConfig = {
// ...
}
module.exports = (env) => {
if (env && env.production) {
return merge(commonConfig, prodConfig)
} else {
return merge(commonConfig, devConfig)
}
}
复制代码
// package.json
{
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.common.conf.js",
"build": "webpack --env.production --config ./build/webpack.common.conf.js"
}
}
复制代码
// /src/index.js
export function add(a, b) {
return a + b
}
export function minus(a, b) {
return a - b
}
export function multiply(a, b) {
return a * b
}
export function division(a, b) {
return a / b
}
复制代码
// /webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js-math.js',
library: 'jsMath',
libraryTarget: 'umd'
}
}
复制代码
// /dist/index.html
<script src="./js-math.js"></script>
<script>
console.log(jsMath.add(2, 4)) // 6
</script>
复制代码
// ES2015 module import:
import jsMath from 'js-math'
jsMath.add(2, 3)
// CommonJS module require:
const jsMath = require('js-math')
jsMath.add(2, 3)
// AMD module require:
require(['js-math'], function(jsMath) {
jsMath.add(2, 3)
})
复制代码
libraryTarget: 'var' // 让 library 的值做为全局变量使用
libraryTarget: 'this' // 将 library 的值挂载到 this 对象上使用
libraryTarget: 'window' // 将 library 的值挂载到 window 对象上使用
libraryTarget: 'umd' // 使其支持在 ES2015/CommonJS/AMD 中使用
复制代码
import _ from 'lodash'
// /webpack.config.js
module.exports = {
// externals: ['lodash'] // 表示咱们的库在打包时不把 lodash 打包进去,而是让业务代码去加载 lodash
externals: { // 详细配置
lodash: {
root: '_', // 表示若是 lodash 是经过 script 标签引入的,必须在页面上注入一个名为 _ 的全局变量,这样才能正确执行
commonjs: 'lodash' // 表示经过 CommonJS 这种写法去加载时,名称必须起为 lodash,如:const lodash = require('lodash')
}
}
}
复制代码
// /package.json
{
"main": "./dist/js-math.js"
}
复制代码
去npm 官网注册 npm 账号
运行命令 npm adduser # 添加用户信息 npm publish # 将库上传到 npm
npm install js-math # 安装使用
npm i -D workbox-webpack-plugin # 安装模块包
配置:
// /build/webpack.prod.conf.js
const WorkboxPlugin = require('workbox-webpack-plugin')
module.exports = {
plugins: [
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
]
}
复制代码
// /src/index.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(registration => {
console.log('service-worker registed')
})
.catch(error => {
console.log('service-worker register error')
})
})
}
复制代码
npm run build # 打包项目 打包后会多出两个文件:precache-manifest.js 和 service-worker.js
启动一个服务,访问打包后的项目 断开服务,刷新浏览器,项目仍能正常访问
npm i -D http-server # 安装模块包
配置命令,在 dist 目录下启动一个服务
// /package.json
{
"scripts": {
"httpServer": "http-server dist"
}
}
复制代码
http://127.0.0.1:8080/index.html
注:要在访问地址后加 /index.html,不然可能会出现报错npm i -D typescript ts-loader # 安装模块包
编写代码及配置
// /src/index.tsx
class Greeter {
greeting: string
constructor(message: string) {
this.greeting = message
}
greet() {
return 'Hello, ' + this.greeting
}
}
let greeter = new Greeter('world')
// let greeter = new Greeter(123) // 因为 Greeter 中限定了数据类型为 string,这里若是传非 string 的数据,就会在代码中报错
alert(greeter.greet())
复制代码
// /webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.tsx',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
}
}
复制代码
// /tsconfig.json
{
"compilerOptions": {
"outDir": "./dist", // 用 ts-loader 作 TypeScript 代码打包时,将打包生成的文件放到 ./dist 目录下(不写也行,由于 webpack.config.js 中已经配置了)
"module": "es6", // 指的是用 ES Module 模块的引用方式(即:若是在 index.tsx 文件里引入其它模块的话,须要经过 import ... 这种方式去引入)
"target": "es5", // 指的是打包 TypeScript 语法时,要将最终的语法转换成什么形式
"allowJs": true // 容许在 TypeScript 语法里引入 js 模块
}
}
复制代码
虽然在写 TypeScript 代码时,会有很好的错误提示,但有时在 TypeScript 代码中引入一些其它的库,调用其它库的方法时,并无错误提示,须要执行如下步骤:
若是不肯定是否有对应库的类型文件的支持,能够在GitHub上搜索 DefinitelyTyped,打开后下面有个 TypeSearch 的连接,去 TypeSearch 页面里搜索,若是搜索到了,就说明它有对应库的类型文件的支持,而后安装便可
// /src/index.js // 使用 axios 模拟请求
import axios from 'axios'
axios.get('/api/data.json').then(res => {
console.log(res)
})
复制代码
// /webpack.config.js
module.exports = {
devServer: {
proxy: {
// '/api': 'http://...' // 简写,若是请求是以 /api 开头的,就将其代理到 http://... 进行请求
'/api': {
target: 'http://...', // 若请求地址以 /api 开头,将其代理到 http://... 进行请求
secure: false, // 若是请求地址是 https 的话,须要配置此项
pathRewrite: { // 对一些请求路径的重写
'data.json': 'data-test.json'
},
changeOrigin: true, // 能够帮助咱们改变请求里的 origin,跳过一些服务端的 origin 验证
headers: { // 请求转发时改变请求头,模拟一些数据
host: 'www...',
cookie: '123...'
}
}
}
}
}
复制代码
// /src/home.js
import React, { Component } from 'react'
class Home extends Component {
render() {
return <div>HomePage</div>
}
}
export default Home
复制代码
// /src/list.js
import React, { Component } from 'react'
class List extends Component {
render() {
return <div>ListPage</div>
}
}
export default List
复制代码
// /src/index.js
import React, { Component } from 'react' // 须要安装 react 库
import { BrowserRouter, Route } from 'react-router-dom' // 须要安装 react-router-dom 库
import ReactDom from 'react-dom' // 须要安装 react-dom 库
import Home from './home.js'
import List from './list.js'
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route path="/" exact component={Home} />
<Route path="/list" component={List} />
</div>
</BrowserRouter>
)
}
}
ReactDom.render(<App />, document.getElementById('root')) // 须要在 html 中写一个 id 为 root 的容器
复制代码
// /webpack.config.js
module.exports = {
devServer: {
historyApiFallback: true // 只需配置该参数,便可经过不一样的路由加载不一样的 js
// 注意:这种方法只适用于开发环境中,上线使用须要后端作路由映射处理
}
}
复制代码
// /.eslintrc.js
module.exports = {
env: { // 指定代码的运行环境。不一样的运行环境,全局变量不同,指明运行环境这样 ESLint 就能识别特定的全局变量。同时也会开启对应环境的语法支持
browser: true,
es6: true,
},
extends: [
'plugin:vue/essential',
'airbnb-base',
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: [
'vue',
],
rules: { // 这里能够对规则进行细致的定义,覆盖 extends 中的规则
},
};
复制代码
// /webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', {
loader: 'eslint-loader', // 在 babel-loader 处理以前先用 eslint-loader 检测一下
options: {
fix: true, // 做用同 npx eslint --fix ...
cache: true // 能够下降 ESLint 对打包过程性能的损耗
// force: 'pre' // 无论 eslint-loader 放在什么位置,强制它最早执行
}
}]
}
]
},
devServer: {
overlay: true // 配置此项后,【开发环境】在浏览器打开项目时,eslint 检查的一些报错信息就会以浮层的形式在浏览器中展现
}
}
复制代码
// /webpack.config.js
const path = require('path')
module.exports = {
module: {
rules: [{
exclude: /node_modules/ // 排除应用规则的目录
// include: path.resolve(__dirname, './src') // 限定应用规则的目录
}]
}
}
复制代码
// /webpack.config.js
module.exports = {
resolve: {
extensions: ['.js', '.jsx'], // 当咱们引入一个组件,未指定后缀时(如:import Child from './child/child'),它会自动先去找 ./child/child.js,若是没有,再去找 ./child/child.jsx,合理的配置可减小查找匹配的次数,下降性能损耗
mainFiles: ['index', 'child'], // 配置该项后,当咱们引入一个文件夹路径时(如:import Child from './child/'),它就会自动先去找该文件夹下的 index,若是没有,再去找 child。同上,该配置不易过多,不然影响性能
alias: { // 配置别名
child: path.resolve(__dirname, './src/child') // 使用时就能够这样写:import Child from 'child'
}
}
}
复制代码
// /build/webpack.dll.js
const path = require('path')
module.exports = {
mode: 'production',
entry: {
vendors: ['lodash', 'jquery']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]' // 打包生成一个库,并暴露在全局变量 [name](即:vendors)中
}
}
复制代码
// /package.json
{
"scripts": {
"build:dll": "webpack --config ./build/webpack.dll.js"
},
"dependencies": {
"jquery": "^3.4.1",
"lodash": "^4.17.15"
}
}
复制代码
// /webpack.config.js
const path = require('path')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
plugins: [
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, './dll/vendors.dll.js') // 指的是要往 HtmlWebpackPlugin 生成的 index.html 里加的内容
})
]
}
复制代码
// /package.json
{
"scripts": {
"build": "webpack"
}
}
复制代码
至此,第三方模块只打包一次,并引入生产打包代码中的目标已经实现了 可是 /src/index.js 中 import _ from 'lodash'
使用的仍是 node_modules 里面的库 接下来须要实现的是:引入第三方模块的时候,让它从 dll 文件里引入,而不是从 node_modules 里引入
// /build/webpack.dll.js
// 经过该配置文件打包会生成相似于库的打包结果
const path = require('path')
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DllPlugin({
// 使用 webpack 自带的插件对打包产生的库文件进行分析
// 把库里面一些第三方模块的映射关系放到 path 对应的文件里
name: '[name]', // 暴露出的 DLL 函数的名称
path: path.resolve(__dirname, '../dll/[name].manifest.json') // 分析结果文件输出的绝对路径
})
]
}
复制代码
// /webpack.config.js
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll/vendors.manifest.json')
})
]
}
复制代码
// /build/webpack.dll.js
module.exports = {
entry: {
vendors: ['lodash', 'jquery'],
react: ['react', 'react-dom']
}
}
复制代码
结合 5.3 的配置,此时打包输出的文件有: /dll/vendors.dll.js
/dll/vendors.manifest.json
/dll/react.dll.js
/dll/react.manifest.json
而后配置 /webpack.config.js
// /webpack.config.js
module.exports = {
plugins: [
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, './dll/vendors.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll/vendors.manifest.json')
}),
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, './dll/react.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll/react.manifest.json')
})
]
}
复制代码
注:若是打包生成的 dll 文件有不少,就须要在 /webpack.config.js 中添加不少的 plugin,为了简化代码,能够借助 node 去分析 dll 文件夹下的文件,循环处理,代码以下:
// /webpack.config.js
const fs = require('fs') // 借助 node 中的 fs 模块去读取 dll 文件夹
const plugins = [ // 初始存入一些基础的 plugin
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
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)
}))
}
})
module.exports = {
plugins
}
复制代码
// /src/index.js
console.log('home page')
// /src/list.js
console.log('list page')
复制代码
// /build/webpack.common.conf.js
module.exports = {
entry: { // 配置多个入口文件
main: './src/index.js',
list: './src/list.js'
},
plugins: [
// 配置多个打包输出页面
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'index.html',
chunks: ['runtime', 'vendors', 'main'] // 不一样的页面引入不一样的入口文件(如有 runtime 或者 vendors 就引入,没有就不写)
}),
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'list.html',
chunks: ['runtime', 'vendors', 'list']
})
]
}
复制代码
如上,若是每增长一个页面,就手动增长代码的话,就会致使大量重复代码,下面开始对打包配置代码进行优化:
// /build/webpack.common.conf.js
const fs = require('fs')
const makePlugins = configs => { // 自定义方法 makePlugins,用于动态生成 plugins
const plugins = [
// 初始能够存一些基本的 plugin,如:CleanWebpackPlugin
]
// 根据不一样的入口文件,生成不一样的 html
// Object.keys() 方法会返回一个由给定对象枚举属性组成的数组
Object.keys(configs.entry).forEach(item => {
plugins.push(new HtmlWebpackPlugin({
template: 'src/index.html',
filename: `${item}.html`,
chunks: [item]
}))
})
// 动态添加并使用打包生成的一些第三方 dll 库
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
}
const configs = {
// 将 module.exports 导出的一堆配置放到变量 configs 里
entry: {
index: './src/index.js',
list: './src/list.js'
}
// ...
// 这里不写 plugins,经过一个方法去生成 plugins
}
configs.plugins = makePlugins(configs) // 调用 makePlugins 自定义的方法,生成 plugins
module.exports = configs // 导出重组好的 configs
复制代码
// /src/index.js
console.log('hello world !')
复制代码
// /loaders/replaceLoader.js
module.exports = function(source) {
return source.replace('hello', '你好') // 对源代码执行一个替换
}
复制代码
// /package.json
{
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1"
}
}
复制代码
// /webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js$/,
use: [
path.resolve(__dirname, './loaders/replaceLoader.js')
]
}]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
复制代码
在 /loaders/replaceLoader.js 中,除了经过 return 返回处理后的源代码以外,还可使用 this.callback 作返回处理
// /loaders/replaceLoader.js
module.exports = {
const result = source.replace('hello', '你好')
this.callback(null, result)
}
复制代码
// /loaders/replaceLoader.js
module.exports = function(source) {
// 可经过 this.query 获取使用 loader 时 options 里面传递的配置
console.log(this.query) // { name: 'xiaoli' }
return source.replace('hello', '你好')
}
复制代码
// /webpack.config.js
const path = require('path')
module.exports = {
module: {
rules: [{
test: /\.js$/,
use: [{
loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
options: {
name: 'xiaoli'
}
}]
}]
}
}
复制代码
// /loaders/replaceLoader.js
const loaderUtils = require('loader-utils')
module.exports = {
const options = loaderUtils.getOptions(this)
console.log(options) // { name: 'xiaoli' }
}
复制代码
若是 loader 里调用一些异步的操做(好比延迟 return),打包就会报错,说 loader 没有返回内容,须要使用 this.async()
// /loaders/replaceLoaderAsync.js
module.exports = function(source) {
const callback = this.async()
setTimeout(() => {
const result = source.replace('hello', '你好')
callback(null, result) // 这样调用的 callback 实际上就是 this.callback()
}, 1000)
}
复制代码
// /loaders/replaceLoaderAsync.js
module.exports = function(source) {
const callback = this.async()
setTimeout(() => {
const result = source.replace('hello', '你好')
callback(null, result)
}, 1000)
}
复制代码
// /loaders/replaceLoader.js
module.exports = function(source) {
const result = source.replace('world', '世界')
this.callback(null, result)
}
复制代码
// /webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.js$/,
use: [{
loader: path.resolve(__dirname, './loaders/replaceLoader.js')
}, {
loader: path.resolve(__dirname, './loaders/replaceLoaderAsync.js')
}]
}]
}
}
复制代码
// /webpack.config.js
module.exports = {
resolveLoader: {
// 当你引入一个 loader 的时候,它会先到 node_modules 里面去找,若是找不到,再去 loaders 目录下去找
modules: ['node_modules', './loaders']
},
module: {
rules: [{
test: /\.js$/,
use: [{
loader: 'replaceLoader'
}, {
loader: 'replaceLoaderAsync'
}]
}]
}
}
复制代码
// /plugins/copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
// 构造函数
constructor() {
console.log('插件被使用了')
}
// 当调用插件时,会执行 apply 方法,该方法接收一个参数 compiler,能够理解为 webpack 的实例
apply(compiler) {}
}
module.exports = CopyrightWebpackPlugin
复制代码
// /src/index.js
console.log('hello world !')
复制代码
// /package.json
{
"name": "plugin",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
}
}
复制代码
// /webpack.config.js
const path = require('path')
const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin.js')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [new CopyrightWebpackPlugin()],
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
复制代码
// /webpack.config.js
const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin.js')
module.exports = {
plugins: [new CopyrightWebpackPlugin({
name: 'li' // 这里传递参数
})]
}
复制代码
// /plugins/copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
constructor(options) {
// 经过 options 接收参数
console.log(options)
}
apply(compiler) {}
}
module.exports = CopyrightWebpackPlugin
复制代码
// /plugins/copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
apply(compiler) {
// emit 是指当你把打包的资源放到目标文件夹的时刻
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, callback) => {
console.log('插件执行了')
callback()
})
}
}
module.exports = CopyrightWebpackPlugin
复制代码
// /plugins/copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, callback) => {
// 打包生成的全部内容是存放在 compilation.assets 里面的
// 在 emit 时刻的时候,向打包生成的内容里增长一个 copyright.txt 文件
compilation.assets['copyright.txt'] = {
// 文件里的内容
source: function() {
return 'copyright text ...'
},
// 文件的大小
size: function() {
return 18
}
}
callback()
})
}
}
复制代码
// /package.json
{
"scripts": {
"debug": "node --inspect --inspect-brk node_modules/webpack/bin/webpack.js"
}
}
复制代码
done (异步时刻)表示打包完成
compile (同步时刻)
同步时刻是 tap 且没有 callback
异步时刻是 tapAsync 有 callback
compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
console.log('compile 时刻执行')
})
复制代码
// /src/word.js
export const word = 'hello'
复制代码
// /src/message.js
import { word } from './word.js'
// 须要写 .js 后缀,由于没有使用 webpack
const message = `say ${word}`
export default message
复制代码
// /src/index.js
import message from './message.js'
console.log(message)
复制代码
npm i @babel/parser # 做用是分析代码,产生抽象语法树
npm i @babel/traverse # 做用是帮助咱们快速找到 import 节点
// /bundler.js
// 此文件就是咱们要作的打包工具
// 打包工具是用 nodeJs 来编写的
// node 的一个用于读取文件的模块
const fs = require('fs')
const path = require('path')
// 使用 babelParser 分析代码,产生抽象语法树
const parser = require('@babel/parser')
// 默认导出的内容是 ESModule 的导出,若是想用 export default 导出内容,须要在后面加个 .default
const traverse = require('@babel/traverse').default
const moduleAnalyser = filename => {
// 以 utf-8 编码读取入口文件的内容
const content = fs.readFileSync(filename, 'utf-8')
// console.log(content)
// 分析文件内容,输出抽象语法树
const ast = parser.parse(content, {
sourceType: 'module'
})
// ast.program.body 便是文件内容中的节点
// console.log(ast.program.body)
const dependencies = {}
// 对抽象语法树进行遍历,找出 Node.type === 'ImportDeclaration' 的元素,并作处理
traverse(ast, {
ImportDeclaration({ node }) {
// console.log(node)
const dirname = path.dirname(filename)
const newFile = './' + path.join(dirname, node.source.value)
dependencies[node.source.value] = newFile
}
})
// console.log(dependencies)
// 将入口文件和对应依赖返回出去
return {
filename, // 入口文件
dependencies // 入口文件里的依赖
}
}
// 传入口文件,调用方法
moduleAnalyser('./src/index.js')
复制代码
// /bundler.js
const babel = require('@babel/core')
const moduleAnalyser = filename => {
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
// code 就是浏览器能够运行的代码
return {
code
}
}
// 分析转化以后的结果
const moduleInfo = moduleAnalyser('./src/index.js')
console.log(moduleInfo)
复制代码
npm i cli-highlight -g // 安装 cli-highlight node bundler.js | highlight // 运行时在后面加上 | highlight
// /bundler.js
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
const moduleAnalyser = filename => {
const content = fs.readFileSync(filename, 'utf-8')
const ast = parser.parse(content, {
sourceType: 'module'
})
const dependencies = {}
traverse(ast, {
ImportDeclaration({ node }) {
const dirname = path.dirname(filename)
const newFile = './' + path.join(dirname, node.source.value)
dependencies[node.source.value] = newFile
}
})
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
return {
filename,
dependencies,
code
}
}
const makeDependenciesGraph = entry => {
const entryModule = moduleAnalyser(entry)
const graphArray = [ entryModule ]
// 循环遍历依赖中的依赖
for(let i = 0; i < graphArray.length; i++) {
const item = graphArray[i]
const { dependencies } = item
if (dependencies) {
for(let j in dependencies) {
graphArray.push(
moduleAnalyser(dependencies[j])
)
}
}
}
// 将遍历后的依赖数组转化成对象的形式
const graph = {}
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
})
return graph
}
// 最终分析生成的代码和依赖信息
const graphInfo = makeDependenciesGraph('./src/index.js')
console.log(graphInfo)
复制代码
// /bundler.js
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
const moduleAnalyser = filename => {
const content = fs.readFileSync(filename, 'utf-8')
const ast = parser.parse(content, {
sourceType: 'module'
})
const dependencies = {}
traverse(ast, {
ImportDeclaration({ node }) {
const dirname = path.dirname(filename)
const newFile = './' + path.join(dirname, node.source.value)
dependencies[node.source.value] = newFile
}
})
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
return {
filename,
dependencies,
code
}
}
const makeDependenciesGraph = entry => {
const entryModule = moduleAnalyser(entry)
const graphArray = [ entryModule ]
for(let i = 0; i < graphArray.length; i++) {
const item = graphArray[i]
const { dependencies } = item
if (dependencies) {
for(let j in dependencies) {
graphArray.push(
moduleAnalyser(dependencies[j])
)
}
}
}
const graph = {}
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
})
return graph
}
const generateCode = entry => {
const graph = JSON.stringify(makeDependenciesGraph(entry))
return `
(function(graph) {
function require(module) {
function localRequire(relativePath) {
return require(graph[module].dependencies[relativePath])
}
var exports = {}
(function(require, exports, code) {
eval(code)
})(localRequire, exports, graph[module].code)
return exports
}
require('${entry}')
})(${graph})
`
}
const code = generateCode('./src/index.js')
console.log(code)
复制代码
(function(graph) {
function require(module) {
function localRequire(relativePath) {
return require(graph[module].dependencies[relativePath]);
};
var exports = {};
(function(require, exports, code) {
eval(code);
})(localRequire, exports, graph[module].code);
return exports;
};
require('./src/index.js');
})({"./src/index.js":{"dependencies":{"message.js":"./src\\message.js"},"code":"\"use strict\";\n\nvar _message = _interopRequireDefault(require(\"message.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log(_message[\"default\"]);"},"./src\\message.js":{"dependencies":{"./word.js":"./src\\word.js"},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nvar _word = require(\"./word.js\");\n\nvar message = \"say \".concat(_word.word);\nvar _default = message;\nexports[\"default\"] = _default;"},"./src\\word.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.word = void 0;\nvar word = 'hello';\nexports.word = word;"}});
复制代码