- 安裝前先npm初始化
- 本地服務(wù)
- 復(fù)制html
- 處理css
- 處理less
- 抽離css文件界拦,通過link引入
- 壓縮css和js
- 給css加上兼容瀏覽器的前綴
- es6 轉(zhuǎn) es5
- es 7的語法
- 全局變量引入
- webpack圖片打包
- 當(dāng)圖片小于多少荣挨,用base64
- 打包文件分類
- 希望輸出的時候够吩,給這些
css\img
加上前綴,傳到服務(wù)器也能訪問 - 如果只希望處理圖片
- 打包多頁應(yīng)用
- 配置
source-map
watch
改完代表重新打包實體webpack
的其他三個小插件webpack
跨域- 如果后端給的請求沒有API 「跨域」
- 前端只想單純mock數(shù)據(jù) 「跨域」
- 有服務(wù)端锭碳,不用代理, 服務(wù)端啟動webpack 「跨域」
- webpack解析resolve
- 但是每次引入都很長凿将,如何優(yōu)雅引入
- 省略擴展名
- 定義環(huán)境變量
- 區(qū)分兩個不同的環(huán)境
- webpack 優(yōu)化
- 優(yōu)化:當(dāng)某些包是獨立的個體沒有依賴
- 優(yōu)化:規(guī)則匹配設(shè)置范圍
- 優(yōu)化:忽略依賴中不必要的語言包
- 動態(tài)鏈接庫
- 多線程打包happypack
- webpack 自帶的優(yōu)化
- 抽取公共代碼
- 懶加載(延遲加載)
- 熱更新(當(dāng)頁面改變只更新改變的部分此叠,不重新打包)
- tapable介紹 - SyncHook
- tapable介紹 - SyncBailHook
- tapable介紹 - SyncWaterfallHook
- tapable介紹 - SyncLoopHook
-
AsyncParallelHook
與AsyncParallelBailHook
- 異步串行 —— AsyncSeriesHook
- 異步串行 —— AsyncSeriesWaterfallHook
- 手寫webpack
- webpack分析及處理
- 創(chuàng)建依賴關(guān)系
- ast遞歸解析
- 生成打包工具
- 增加loader
- 增加plugins
- loader
- 配置多個loader
babel-loader
實現(xiàn)banner-loader
實現(xiàn)(自創(chuàng))- 實現(xiàn)
file-loader
和url-loader
less-loader
和css-loader
css-loader
- webpack 中的插件
- 文件列表插件
- 內(nèi)聯(lián)的
webpack
插件 - 打包后自動發(fā)布
安裝前先npm初始化
npm init -y
npm i webpack webpack-cli -D
let path = require('path') // 相對路徑變絕對路徑
module.exports = {
mode: 'production', // 模式 默認 production development
entry: './src/index', // 入口
output: {
filename: 'bundle.[hash:8].js', // hash: 8只顯示8位
path: path.resolve(__dirname, 'dist'),
publicPath: '' // // 給所有打包文件引入時加前綴,包括css仁讨,js羽莺,img,如果只想處理圖片可以單獨在url-loader配置中加publicPath
}
}
本地服務(wù)
npm i webpack-dev-server -D
devServer: {
port: 3000,
progress: true // 滾動條
contentBase: './build' // 起服務(wù)的地址
open: true // 自動打開瀏覽器
compress: true // gzip壓縮
}
復(fù)制html
npm i html-webpack-plugin -D
let HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [ // 放著所有webpack插件
new HtmlWebpackPlugin({ // 用于使用模板打包時生成index.html文件洞豁,并且在run dev時會將模板文件也打包到內(nèi)存中
template: './index.html', // 模板文件
filename: 'index.html', // 打包后生成文件
hash: true, // 添加hash值解決緩存問題
minify: { // 對打包的html模板進行壓縮
removeAttributeQuotes: true, // 刪除屬性雙引號
collapseWhitespace: true // 折疊空行變成一行
}
})
]
處理css
npm i css-loader style-loader -D
// css-loader 作用:用來解析@import這種語法
// style-loader 作用:把 css 插入到head標(biāo)簽中
// loader的執(zhí)行順序: 默認是從右向左(從下向上)
module: { // 模塊
rules: [ // 規(guī)則
// style-loader 把css插入head標(biāo)簽中
// loader 功能單一
// 多個loader 需要 []
// 順便默認從右到左
// 也可以寫成對象方式
{
test: /\.css$/, // css 處理
// use: 'css-loader'
// use: ['style-loader', 'css-loader'],
use: [
// {
// loader: 'style-loader',
// options: {
// insertAt: 'top' // 將css標(biāo)簽插入最頂頭 這樣可以自定義style不被覆蓋
// }
// },
MiniCssExtractPlugin.loader,
'css-loader', // css-loader 用來解析@import這種語法,
'postcss-loader'
]
}
]
}
處理less
npm i less-loader
{
test: /\.less$/, // less 處理
// use: 'css-loader'
// use: ['style-loader', 'css-loader'],
use: [
// {
// loader: 'style-loader',
// options: {
// insertAt: 'top' // 將css標(biāo)簽插入最頂頭 這樣可以自定義style不被覆蓋
// }
// },
MiniCssExtractPlugin.loader, // 這樣相當(dāng)于抽離成一個css文件盐固, 如果希望抽離成分別不同的css, 需要再引入MiniCssExtractPlugin,再配置
'css-loader', // css-loader 用來解析@import這種語法
'postcss-loader',
'less-loader' // less-loader less -> css
// sass node-sass sass-loader
// stylus stylus-loader
]
}
抽離css文件丈挟,通過link引入
yarn add mini-css-extract-plugin -D
let MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 壓縮css
plugins: [
new MiniCssExtractPlugin({
filename: 'css/main.css'
})
]
{
test: /\.css$/, // css 處理
// use: 'css-loader'
// use: ['style-loader', 'css-loader'],
use: [
// {
// loader: 'style-loader',
// options: {
// insertAt: 'top' // 將css標(biāo)簽插入最頂頭 這樣可以自定義style不被覆蓋
// }
// },
// 此時不需要style-loader
MiniCssExtractPlugin.loader, // 抽離
'css-loader', // css-loader 用來解析@import這種語法,
'postcss-loader'
]
}
抽離css插件文件時可使用optimize-css-assets-webpack-plugin
優(yōu)化壓縮css以及js文件
壓縮css和js
// 用了`mini-css-extract-plugin`抽離css為link需使用`optimize-css-assets-webpack-plugin`進行壓縮css,使用此方法壓縮了css需要`uglifyjs-webpack-plugin`壓縮js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
module.exports = {
optimization: { // 優(yōu)化項
minimizer: [
new UglifyJsPlugin({ // 優(yōu)化js
cache: true, // 是否緩存
parallel: true, // 是否并發(fā)打包
// sourceMap: true // 源碼映射 set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({}) // css 的優(yōu)化
]
},
mode: 'production',
entry: '',
output: {},
}
給css加上兼容瀏覽器的前綴
yarn add postcss-loader autoprefixer -D
// css
{
test: /\.css$/, // css 處理
// use: 'css-loader'
// use: ['style-loader', 'css-loader'],
use: [
// {
// loader: 'style-loader',
// options: {
// insertAt: 'top' // 將css標(biāo)簽插入最頂頭 這樣可以自定義style不被覆蓋
// }
// },
MiniCssExtractPlugin.loader,
'css-loader', // css-loader 用來解析@import這種語法,
'postcss-loader'
]
}
// less
{
test: /\.less$/, // less 處理
// use: 'css-loader'
// use: ['style-loader', 'css-loader'],
use: [
// {
// loader: 'style-loader',
// options: {
// insertAt: 'top' // 將css標(biāo)簽插入最頂頭 這樣可以自定義style不被覆蓋
// }
// },
MiniCssExtractPlugin.loader, // 這樣相當(dāng)于抽離成一個css文件刁卜, 如果希望抽離成分別不同的css, 需要再引入MiniCssExtractPlugin,再配置
'css-loader', // css-loader 用來解析@import這種語法
'postcss-loader',
'less-loader' // less-loader less -> css
// sass node-sass sass-loader
// stylus stylus-loader
]
},
postcss 需要配置文檔 postcss.config1.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
es6 轉(zhuǎn) es5
npm i babel-loader @babel/core @babel/preset-env -D
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [ //預(yù)設(shè)
'@babel/preset-env'
],
plugins:[
// 轉(zhuǎn)es7的語法
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
},
exclude: /node_modules/
}
]
}
}
轉(zhuǎn)es7的語法
// 轉(zhuǎn)class
npm i @babel/plugin-proposal-class-properties -D
// 轉(zhuǎn)裝飾器
npm i @babel/plugin-proposal-decorators -D
配置如上
其他不兼容的高級語法
使用 @babel/polyfill
語法檢查 eslint
npm i eslint eslint-loader -S
根目錄添加 .eslintrc.json
配置文件
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
enforce: 'pre' // previous優(yōu)先執(zhí)行 post-普通loader之后執(zhí)行
}
}
},
{
test: /\.js$/, // mormal 普通的loader
use: {
loader: 'babel-loader',
options: {
presets: [ //預(yù)設(shè)
'@babel/preset-env'
]
}
},
exclude: /node_modules/
}
]
}
}
全局變量引入
jquery的引入
npm i jquery -S
let webpack = require('webpack')
new webpack.ProvidePlugin({
$: 'jquery'
})
其他情況
- 暴露全局
npm i expose-loader -D
暴露全局的loader
法1:
可以在js中 import $ from 'expose-loader?$!jquery'
// 全局暴露jquery為$符號
可以調(diào)用window.$
法2:
也可在webpack.config.js
中配置 rules
module.exports = {
module: {
rules: [
{
test: require.resolve('jquery'),
use: 'expose-loader?$'
}
]
}
}
以后在.js
文件中引入
import $ from 'jquery'
法3. 如何在每個模塊中注入:
let webpack = require('webpack')
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery'
})
]
}
之后代碼內(nèi)直接使用 $
法4:
在index.html
中通過script
標(biāo)簽引入jquery
, 但是在js
中曙咽,用import
會重新打包jquery
,如何避免
從輸出的bundle 中排除依賴
module.exports = {
externals: { // 告知webpack是外部引入的蛔趴,不需要打包
jquery: 'jQuery'
}
}
此時在index.js上
import $ from 'jquery'
console.log($)
webpack圖片打包
- js中創(chuàng)建
- css中引入
<img src="">
yarn add file-loader -D
適合一二情況
module.export={
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: 'file-loader'
}
]
}
}
默認會內(nèi)部生成一張圖片到build,生成圖片的路徑返回回來
第一種情況: 圖片地址要import
引入,直接寫圖片的地址例朱,會默認為字符串
import logo from './logo.png'
let image = new Image()
image.src = logo
document.body.appendChild(image)
第二種情況: css-loader
會將css
里面的圖片轉(zhuǎn)為require
的格式
div {
background: url("./logo.png");
}
第三種情況: 解析html
中的image
yarn add html-withimg-loader -D
{
test: /\.html$/,
use: 'html-withimg-loader'
}
當(dāng)圖片小于多少孝情,用base64
yarn add url-loader -D
如果過大,才用file-loader
{
test: /\.(png|jpg|gif)$/,
// 當(dāng)圖片小于多少洒嗤,用base64,否則用file-loader產(chǎn)生真實的圖片
use: {
loader: 'url-loader',
options: {
limit: 200 * 1024, // 小于200k變成base64
// outputPath: '/img/', // 打包后輸出地址
// publicPath: '' // 給資源加上域名路徑
}
}
}
打包文件分類
1.圖片:
{
test: /\.(png|jpg|gif)$/,
// 當(dāng)圖片小于多少咧叭,用base64,否則用file-loader產(chǎn)生真實的圖片
use: {
loader: 'url-loader',
options: {
limit: 1, // 200k 200 * 1024
outputPath: 'img/' // 打包后輸出地址 在dist/img
}
}
},
2.css:
plugins: [
new MiniCssExtractPlugin({
filename: 'css/main.css'
}),
]
希望輸出的時候,給這些css\img
加上前綴烁竭,傳到服務(wù)器也能訪問
output: {
filename: 'bundle.[hash:8].js', // hash: 8只顯示8位
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://www.mayufo.cn' // 給靜態(tài)資源統(tǒng)一加
},
如果只希望處理圖片
{
test: /\.(png|jpg|gif)$/,
// 當(dāng)圖片小于多少,用base64,否則用file-loader產(chǎn)生真實的圖片
use: {
loader: 'url-loader',
options: {
limit: 1, // 200k 200 * 1024
outputPath: '/img/', // 打包后輸出地址
publicPath: 'http://www.mayufo.cn'
}
}
}
打包多頁應(yīng)用
// 多入口
let path = require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
home: './src/index.js',
other: './src/other.js'
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, 'dist2')
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'home.html',
chunks: ['home']
}),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'other.html',
chunks: ['other', 'home'] // other.html 里面有 other.js & home.js
}),
]
}
配置source-map
yarn add @babel/core @babel/preset-env babel-loader webpack-dev-server -D
module.exports = {
devtool: 'source-map' // 增加映射文件調(diào)試源代碼
}
- 源碼映射 會標(biāo)識錯誤的代碼 打包后生成獨立的文件 大而全 「source-map」
- 不會陳勝單獨的文件 但是可以顯示行和列 「eval-source-map」
- 不會產(chǎn)生列有行吉挣,產(chǎn)生單獨的映射文件 「cheap-module-source-map」
- 不會產(chǎn)生文件 集成在打包后的文件中 不會產(chǎn)生列有行 「cheap-module-eval-source-map」
watch
改完代表重新打包實體
module.exports = {
watch: true,
watchOptions: {
poll: 1000, // 每秒監(jiān)聽1000次
aggregateTimeout: 300, // 防抖派撕,當(dāng)?shù)谝粋€文件更改婉弹,會在重新構(gòu)建前增加延遲
ignored: /node_modules/ // 對于某些系統(tǒng),監(jiān)聽大量文件系統(tǒng)會導(dǎo)致大量的 CPU 或內(nèi)存占用终吼。這個選項可以排除一些巨大的文件夾镀赌,
},
}
webpack
的其他三個小插件
cleanWebpackPlugin
每次打包之前刪掉dist目錄
yarn add clean-webpack-plugin -D
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
output: {
path: path.resolve(process.cwd(), 'dist'),
},
plugins: [
new CleanWebpackPlugin('./dist')
]
}
copyWebpackPlugin
一些靜態(tài)資源也希望拷貝的dist中
yarn add copy-webpack-plugin -D
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new CopyWebpackPlugin([
{from: 'doc', to: './dist'}
])
]
}
-
bannerPlugin
內(nèi)置模塊
版權(quán)聲明
const webpack = require('webpack');
new webpack.BannerPlugin('hello world')
// or
new webpack.BannerPlugin({ banner: 'hello world'})
webpack
跨域
設(shè)置一個服務(wù),由于webpack-dev-server
內(nèi)含express
server.js
// express
let express = require('express')
let app = express();
app.get('/api/user', (res) => {
res.json({name: 'mayufo'})
})
app.listen(3000) // 服務(wù)端口在3000
寫完后記得node server.js
訪問 http://localhost:3000/api/user
可見內(nèi)容
index.js
// 發(fā)送一個請求
let xhr = new XMLHttpRequest();
// 默認訪問 http://localhost:8080 webpack-dev-server 的服務(wù) 再轉(zhuǎn)發(fā)給3000
xhr.open('GET', '/api/user', true);
xhr.onload = function () {
console.log(xhr.response)
}
xhr.send();
webpack.config.js
module.exports = {
devServer: {
proxy: {
'/api': 'http://localhost:3000'
}
},
}
1.如果后端給的請求沒有API 「跨域」
// express
let express = require('express')
let app = express();
app.get('/user', (res) => {
res.json({name: 'mayufo'})
})
app.listen(3000) // 服務(wù)端口在3000
請求已api開頭, 轉(zhuǎn)發(fā)的時候再刪掉api
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {'^/api': ''}
}
}
}
2.前端只想單純mock數(shù)據(jù) 「跨域」
devServer: {
// proxy: {
// '/api': 'http://localhost:3000' // 配置一個代理
// }
// proxy: { // 重寫方式 把請求代理到express 上
// '/api': {
// target: 'http://localhost:3000',
// pathRewrite: {'^/api': ''}
// }
// }
before: function (app) { // 勾子
app.get('/api/user', (req, res) => {
res.json({name: 'tigerHee'})
})
}
},
3.有服務(wù)端,不用代理, 服務(wù)端啟動webpack 「跨域」
server.js
中啟動webpack
yarn add webpack-dev-middleware -D
server.js
// express
let express = require('express')
let webpack = require('webpack')
let app = express();
// 中間件
let middle = require('webpack-dev-middleware')
let config = require('./webpack.config')
let compiler = webpack(config)
app.use(middle(compiler))
app.get('/user', (req, res) => {
res.json({name: 'mayufo'})
})
app.listen(3000)
webpack解析resolve
以bootstrap
為例
npm install bootstrap -D
index.js
import 'bootstrap/dist/css/bootstrap.css'
報錯
ERROR in ./node_modules/bootstrap/dist/css/bootstrap.css 7:0
Module parse failed: Unexpected token (7:0)
You may need an appropriate loader to handle this file type.
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
| */
> :root {
| --blue: #007bff;
| --indigo: #6610f2;
@ ./src/index.js 22:0-42
@ multi (webpack)-dev-server/client?http://localhost:8081 ./src/index.js
這是因為bootstrap
4.0的css引入了新的特性际跪,CSS Variables
安裝
npm install postcss-custom-properties --save-dev
配置webpack.config.js
{
test: /\.css$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require("postcss-custom-properties")
]
}
}]
}
但是每次引入都很長商佛,如何優(yōu)雅引入
resolve: {
// 在當(dāng)前目錄查找
modules: [path.resolve('node_modules')],
alias: {
'bootstrapCss': 'bootstrap/dist/css/bootstrap.css'
}
},
import 'bootstrapCss' // 在node_modules查找
省略擴展名
extensions:
resolve: {
// 在當(dāng)前目錄查找
modules: [path.resolve('node_modules')],
// alias: {
// 'bootstrapCss': 'bootstrap/dist/css/bootstrap.css'
// },
mainFields: ['style', 'main'], // 先用bootstrap中在package中的style,沒有在用main
// mainFiles: [] // 入口文件的名字 默認index
extensions: ['.js', '.css', '.json'] // 當(dāng)沒有拓展命的時候,先默認js姆打、次之css良姆、再次之json
},
定義環(huán)境變量
DefinePlugin
允許創(chuàng)建一個在編譯時可以配置的全局常量。這可能會對開發(fā)模式和生產(chǎn)模式的構(gòu)建允許不同的行為非常有用幔戏。
let url = ''
if (DEV === 'dev') {
// 開發(fā)環(huán)境
url = 'http://localhost:3000'
} else {
// 生成環(huán)境
url = 'http://www.mayufo.cn'
}
webpack.config.js
new webpack.DefinePlugin({
// DEV: '"production"',
DEV: JSON.stringify('production'),
FLAG: 'true', // 布爾
EXPRESSION: '1 + 1' // 字符串 如果希望是字符串 JSON.stringify('1 + 1')
})
區(qū)分兩個不同的環(huán)境
分別配置不同的環(huán)境
-
webpack.base4.js
基礎(chǔ)配置 -
webpack.dev4.js
開發(fā)環(huán)境 -
webpack.prod4.js
生產(chǎn)環(huán)境
yarn add webpack-merge -D
npm run build -- -- config webpack.dev4.js
npm run build -- -- config webpack.build.js
webpack.base4.js
let path = require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
let CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: {
home: './src/index.js'
},
output: {
filename: "[name].js",
path: path.resolve(process.cwd(), 'dist3')
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require("postcss-custom-properties")
]
}
}]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
})
]
}
webpack.dev4.js
let merge = require('webpack-merge')
let base = require('./webpack.base4.js')
module.exports = merge(base, {
mode: 'development',
devServer: {},
devtool: 'source-map'
})
webpack.prod4.js
let merge = require('webpack-merge')
let base = require('./webpack.base4.js')
module.exports = merge(base, {
mode: 'production'
})
package.json
"scripts": {
"build": "webpack --config webpack.prod4.js",
"dev": "webpack-dev-server --config webpack.dev4.js"
},
webpack 優(yōu)化
yarn add webpack webpack-cli html-webpack-plugin @babel/core babel-loader @babel/preset-env @babel/preset-react -D
webpack.config.js
let path = require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
]
}
優(yōu)化:當(dāng)某些包是獨立的個體沒有依賴
以jquery為例玛追,yarn add jquery -D
,它是一個獨立的包沒有依賴,可以在webpack配置中闲延,配置它不再查找依賴
module: {
noParse: /jquery/, // 不用解析某些包的依賴
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}
},
]
}
運行npx webpack
從2057ms -> 1946 ms
優(yōu)化:規(guī)則匹配設(shè)置范圍
rules: [
{
test: /\.js$/,
exclude: '/node_modules/', // 排除
include: path.resolve('src'), // 在這個范圍內(nèi)
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}
}
盡量實用include
,不使用exclude
,使用絕對路徑
優(yōu)化:忽略依賴中不必要的語言包
yarn add moment webpack-dev-server -D
忽略掉moment
的其他語言包
let webpack = require('webpack')
plugins: [
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
]
index.js
import moment from 'moment'
let r = moment().endOf('day').fromNow() // 距離現(xiàn)在多少天
console.log(r);
從 1.2MB 到 800kb
動態(tài)鏈接庫
yarn add react react-dom
正常使用
webpack.config.js
{
test: /\.js$/,
exclude: '/node_modules/',
include: path.resolve('src'),
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}
}
index.js
import React from 'react'
import {render} from 'react-dom'
render(<h1>111111</h1>, window.root)
index.html
<div id="root"></div>
獨立的將react react-dom
打包好, 打包好再引用痊剖,從而減少webpack
每次都要打包react
創(chuàng)建webpack.config.react.js
let path = require('path')
let webpack = require('webpack')
module.exports = {
mode: 'development',
entry: {
// test: './src/test.js'
react: ['react', 'react-dom']
},
output: {
filename: '_dll_[name].js', // 產(chǎn)生的文件名
path: path.resolve(__dirname, 'dist'),
library: '_dll_[name]', // 給輸出的結(jié)果加個名字
// libraryTarget: 'var' // 配置如何暴露 library
// commonjs 結(jié)果放在export屬性上, umd統(tǒng)一資源模塊, 默認是var
},
plugins: [
new webpack.DllPlugin({
name: '_dll_[name]', // name === library
path: path.resolve(__dirname, 'dist', 'manifest.json') // manifest.json 定義了各個模塊的路徑
})
]
}
manifest.json
就是一個任務(wù)清單or動態(tài)鏈接庫垒玲,在這個清單里面查找react
npx webpack --config webpack.config.react.js
在index.html
增加引用
<body>
<div id="root"></div>
<script src="/_dll_react.js"></script>
</body>
在webpack.config.js 中配置陆馁,現(xiàn)在動態(tài)鏈接庫manifest.json
中查找,如果沒有再打包react
plugins: [
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, 'dist', 'manifest.json')
})
]
DLLPlugin 和 DLLReferencePlugin
npm run build
打包后的bunle.js
文件變小
npm run dev
可以理解為先把react打包,后面每次都直接使用react打包后的結(jié)果
多線程打包happypack
yarn add happypack
webpack.config.js
let Happypack = require('happypack')
rules: [
{
test: /\.js$/,
exclude: '/node_modules/',
include: path.resolve('src'),
use: 'happypack/loader?id=js'
},
]
plugins: [
new Happypack({
id: 'js',
use: [{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}]
})
]
js啟用多線程合愈,由于啟用多線程也會浪費時間叮贩,因此當(dāng)項目比較大的時候啟用效果更好
css啟用多線程
{
test: /\.css$/,
use: 'happypack/loader?id=css'
}
new Happypack({
id: 'css',
use: ['style-loader', 'css-loader']
}),
webpack 自帶的優(yōu)化
test.js
let sum = (a, b) => {
return a + b + 'sum'
}
let minus = (a, b) => {
return a - b + 'minus';
}
export default {
sum, minus
}
- 使用import
index.js
import calc from './test'
console.log(calc.sum(1, 2));
import在生產(chǎn)環(huán)境下會自動去除沒有用的代碼minus
蝌数,這叫tree-shaking
跪呈,將沒有用的代碼自動刪除掉
index.js
let calc = require('./test')
console.log(calc); // es 6導(dǎo)出,是一個default的對象
console.log(calc.default.sum(1, 2));
require引入es6 模塊會把結(jié)果放在default上,打包build后并不會把多余minus
代碼刪除掉掂摔,不支持tree-shaking
- 作用域的提升
index.js
let a = 1
let b = 2
let c = 3
let d = a + b + c
console.log(d, '---------');
打包出來的文件
console.log(r.default.sum(1,2));console.log(6,"---------")
在webpack中可以省略一些可以簡化的代碼
抽取公共代碼
- 抽離自有模塊
webpack.config.js
module.exports = {
optimization: {
splitChunks: { // 分割代碼塊说莫,針對多入口
cacheGroups: { // 緩存組
common: { // 公共模塊
minSize: 0, // 大于多少抽離
minChunks: 2, // 使用多少次以上抽離抽離
chunks: 'initial' // 從什么地方開始, 從入口開始
}
}
}
},
}
分別有a.js和b.js, index.js和other.js分別引入a和b兩個js
index.js
import './a'
import './b'
console.log('index.js');
other.js
import './a'
import './b'
console.log('other.js');
webpack.config.js
module.exports = {
optimization: {
splitChunks: { // 分割代碼塊杨箭,針對多入口
cacheGroups: { // 緩存組
common: { // 公共模塊
minSize: 0, // 大于多少抽離
minChunks: 2, // 使用多少次以上抽離抽離
chunks: 'initial' // 從什么地方開始, 從入口開始
}
}
}
},
}
- 抽離第三方模塊
比如jquery
index.js
和 other.js
分別引入
import $ from 'jquery'
console.log($);
修改webpack.config.js
配置:
optimization: {
splitChunks: { // 分割代碼塊,針對多入口
cacheGroups: { // 緩存組
common: { // 公共模塊
minSize: 0, // 大于多少抽離
minChunks: 2, // 使用多少次以上抽離抽離
chunks: 'initial' // 從什么地方開始,剛開始
},
vendor: {
priority: 1, // 增加權(quán)重, (先抽離第三方)
test: /node_modules/, // 把此目錄下的抽離
minSize: 0, // 大于多少抽離
minChunks: 2, // 使用多少次以上抽離抽離
chunks: 'initial' // 從什么地方開始,剛開始
}
}
},
},
懶加載(延遲加載)
yarn add @babel/plugin-syntax-dynamic-import -D
source.js
export default 'mayufo'
index.js
let button = document.createElement('button')
button.innerHTML = 'hello'
button.addEventListener('click', function () {
console.log('click')
// es6草案中的語法储狭,jsonp實現(xiàn)動態(tài)加載文件
import('./source.js').then(data => {
console.log(data.default)
})
})
document.body.appendChild(button)
webpack.config.js
{
test: /\.js$/,
exclude: '/node_modules/',
include: path.resolve('src'),
use: [{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
],
plugins: [
'@babel/plugin-syntax-dynamic-import'
]
}
}]
}
熱更新(當(dāng)頁面改變只更新改變的部分互婿,不重新打包)
webpack.config.js
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new webpack.NameModulesPlugin(), // 打印更新的模塊路徑
new webpack.HotModuleReplacementPlugin() // 熱更新插件
]
index.js
import str from './source'
console.log(str);
if (module.hot) {
module.hot.accept('./source', () => {
console.log('文件更新了');
require('./source')
console.log(str);
})
}
tapable介紹 - SyncHook
webpack
本質(zhì)上是一種事件流的機制,它的工作流程就是將各個插件串聯(lián)起來辽狈,而實現(xiàn)這一切的核心就是Tapable
慈参,webpack
中最核心的負責(zé)編譯的Compiler
和負責(zé)創(chuàng)建bundles
的Compilation
都是Tapable
的實例。
SyncHook
不關(guān)心監(jiān)聽函數(shù)的返回值
yarn add tabable
1.use.js
let {SyncHook} = require('tapable') // 結(jié)構(gòu)同步勾子
class Lesson {
constructor () {
this.hooks = {
// 訂閱勾子
arch: new SyncHook(['name']),
}
}
start () {
this.hooks.arch.call('may')
}
tap () { // 注冊監(jiān)聽函數(shù)
this.hooks.arch.tap('node', function (name) {
console.log('node', name)
})
this.hooks.arch.tap('react', function (name) {
console.log('react', name)
})
}
}
let l = new Lesson()
l.tap(); //注冊兩個函數(shù)
l.start() // 啟動勾子
1.theory.js
class SyncHook { // 勾子是同步的
constructor(args) { // args => ['name']
this.tasks = []
}
tap (name, task) {
this.tasks.push(task)
}
call (...args) {
this.tasks.forEach((task) => task(...args))
}
}
let hook = new SyncHook(['name'])
hook.tap('react', function (name) {
console.log('react', name);
})
hook.tap('node', function (name) {
console.log('node', name);
})
hook.call('jw')
tapable介紹 - SyncBailHook
SyncBailHook
為勾子加了個保險刮萌,當(dāng)return
返回不是undefine
就會停止
2.use.js
let {SyncBailHook} = require('tapable') // 解構(gòu)同步勾子
class Lesson {
constructor () {
this.hooks = {
// 訂閱勾子
arch: new SyncBailHook(['name']),
}
}
start () {
// 發(fā)布
this.hooks.arch.call('may')
}
tap () { // 注冊監(jiān)聽函數(shù),訂閱
this.hooks.arch.tap('node', function (name) {
console.log('node', name)
return '停止學(xué)習(xí)' // 會停止
// return undefined
})
this.hooks.arch.tap('react', function (name) {
console.log('react', name)
})
}
}
let l = new Lesson()
l.tap(); //注冊兩個函數(shù)
l.start() // 啟動勾子
2.theory.js
class SyncBailHook { // 勾子是同步的
constructor(args) { // args => ['name']
this.tasks = []
}
tap (name, task) {
this.tasks.push(task)
}
call (...args) {
let ret; // 當(dāng)前函數(shù)的返回值
let index = 0; // 當(dāng)前要執(zhí)行的第一個
do {
ret = this.tasks[index](...args)
} while (ret === undefined && index < this.tasks.length)
}
}
let hook = new SyncBailHook(['name'])
hook.tap('react', function (name) {
console.log('react', name);
return '停止學(xué)習(xí)'
// return undefined
})
hook.tap('node', function (name) {
console.log('node', name);
})
hook.call('jw')
tapable介紹 - SyncWaterfallHook
SyncWaterfallHook
上一個監(jiān)聽函數(shù)的返回值可以傳給下一個監(jiān)聽函數(shù)
3.use.js
let {SyncWaterfallHook} = require('tapable') // 解構(gòu)同步勾子
// waterfall 瀑布
class Lesson {
constructor () {
this.hooks = {
// 訂閱勾子
arch: new SyncWaterfallHook(['name']),
}
}
start () {
// 發(fā)布
this.hooks.arch.call('may')
}
tap () { // 注冊監(jiān)聽函數(shù),訂閱
this.hooks.arch.tap('node', function (name) {
console.log('node', name)
return '學(xué)的不錯'
})
this.hooks.arch.tap('react', function (name) {
console.log('react', name)
})
}
}
let l = new Lesson()
l.tap(); //注冊兩個函數(shù)
l.start() // 啟動勾子
3.theory.js
class SyncWaterfallHook { // 勾子是同步的 - 瀑布
constructor(args) { // args => ['name']
this.tasks = []
}
tap (name, task) {
this.tasks.push(task)
}
call (...args) {
let [first, ...others] = this.tasks;
let ret = first(...args)
others.reduce((a, b) => {
return b(a);
}, ret);
}
}
let hook = new SyncWaterfallHook(['name'])
hook.tap('react', function (name) {
console.log('react', name);
return 'react Ok'
// return undefined
})
hook.tap('node', function (name) {
console.log('node', name);
return 'node Ok'
})
hook.tap('webpack', function (data) {
console.log('webpack', data);
})
hook.call('jw')
tapable介紹 - SyncLoopHook
SyncLoopHook
當(dāng)監(jiān)聽函數(shù)被觸發(fā)的時候驮配,如果該監(jiān)聽函數(shù)返回true
時則這個監(jiān)聽函數(shù)會反復(fù)執(zhí)行,如果返回 undefined
則表示退出循環(huán)
4.use.js
let {SyncLoopHook} = require('tapable') // 解構(gòu)同步勾子
// 不返回undefined 會多次執(zhí)行
class Lesson {
constructor () {
this.index = 0
this.hooks = {
// 訂閱勾子
arch: new SyncLoopHook(['name']),
}
}
start () {
// 發(fā)布
this.hooks.arch.call('may')
}
tap () { // 注冊監(jiān)聽函數(shù),訂閱
this.hooks.arch.tap('node', (name) => {
console.log('node', name)
return ++this.index === 3 ? undefined : '繼續(xù)學(xué)'
})
this.hooks.arch.tap('react', (name) => {
console.log('react', name)
})
}
}
let l = new Lesson()
l.tap(); //注冊兩個函數(shù)
l.start() // 啟動勾子
4.theory.js
class SyncLoopHook { // 勾子是同步的 - 瀑布
constructor(args) { // args => ['name']
this.tasks = []
}
tap (name, task) {
this.tasks.push(task)
}
call (...args) {
this.tasks.forEach(task => {
let ret
do {
ret = task(...args);
} while(ret !== undefined)
})
}
}
let hook = new SyncLoopHook(['name'])
let total = 0
hook.tap('react', function (name) {
console.log('react', name);
return ++total === 3 ? undefined: '繼續(xù)學(xué)'
})
hook.tap('node', function (name) {
console.log('node', name);
})
hook.tap('webpack', function (data) {
console.log('webpack', data);
})
hook.call('jw')
AsyncParallelHook
與 AsyncParallelBailHook
異步的勾子分兩種串行
和并行
并行
等待所有并發(fā)的異步事件執(zhí)行后執(zhí)行回調(diào)
注冊的三種方法
- 異步的注冊方法
tap
- 異步的注冊方法
tapAsync
, 還有個回調(diào)參數(shù) -
topPromise
,注冊promise
調(diào)用的三種
- call (同步)
- callAsync (異步)
- promise (異步)
這里介紹的是異步并行的
AsyncParallelHook
不關(guān)心監(jiān)聽函數(shù)的返回值壮锻。
5.use.js
let {AsyncParallelHook} = require('tapable') // 解構(gòu)同步勾子
// 不返回undefined 會多次執(zhí)行
class Lesson {
constructor() {
this.index = 0
this.hooks = {
// 訂閱勾子
arch: new AsyncParallelHook(['name']),
}
}
start() {
// 發(fā)布callAsync
// this.hooks.arch.callAsync('may', function () {
// console.log('end');
// })
// 另一種發(fā)布promise
this.hooks.arch.promise('may').then(function () {
console.log('end');
}
)
}
tap() { // 注冊監(jiān)聽函數(shù),訂閱
// 注冊tapAsync
// this.hooks.arch.tapAsync('node', (name, callback) => {
// setTimeout(() => {
// console.log('node', name)
// callback()
// }, 1000)
// })
// this.hooks.arch.tapAsync('react', (name, callback) => {
// setTimeout(() => {
// console.log('react', name)
// callback()
// }, 1000)
// })
// 另一種訂閱 tapPromise
this.hooks.arch.tapPromise('node', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name)
resolve()
}, 1000)
})
})
this.hooks.arch.tapPromise('react', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name)
resolve()
}, 1000)
})
})
}
}
let l = new Lesson()
l.tap(); //注冊兩個函數(shù)
l.start() // 啟動勾子
5.theory.js
class AsyncParallelHook { // 勾子是同步的 - 瀑布
constructor(args) { // args => ['name']
this.tasks = []
}
tapAsync(name, task) {
this.tasks.push(task)
}
tapPromise(name, task) {
this.tasks.push(task)
}
callAsync(...args) {
let finalCallback = args.pop() // 拿出最終的函數(shù)
let index = 0
let done = () => { // 類似promise.all的實現(xiàn)
index++;
if (index === this.tasks.length) {
finalCallback();
}
}
this.tasks.forEach(task => {
task(...args, done) // 這里的args 已經(jīng)把最后一個參數(shù)刪掉
})
}
promise(...args) {
let tasks = this.tasks.map(task => task(...args))
return Promise.all(tasks)
}
}
let hook = new AsyncParallelHook(['name'])
// hook.tapAsync('react', function (name, callback) {
// setTimeout(() => {
// console.log('react', name);
// callback()
// }, 1000)
// })
//
// hook.tapAsync('node', function (name, callback) {
// setTimeout(() => {
// console.log('node', name);
// callback()
// }, 1000)
// })
// hook.tapAsync('webpack', function (name, callback) {
// setTimeout(() => {
// console.log('webpack', name);
// callback()
// }, 1000)
// })
hook.tapPromise('react', function (name, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve()
}, 1000)
})
})
hook.tapPromise('node', function (name, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve()
}, 1000)
})
})
//
// hook.callAsync('jw', function () {
// console.log('end');
// })
hook.promise('jw').then(function () {
console.log('end');
})
AsyncParallelBailHook
只要監(jiān)聽函數(shù)的返回值不為 null
琐旁,就會忽略后面的監(jiān)聽函數(shù)執(zhí)行,直接跳躍到callAsync
等觸發(fā)函數(shù)綁定的回調(diào)函數(shù)猜绣,然后執(zhí)行這個被綁定的回調(diào)函數(shù)灰殴。
使用和原理與SyncBailHook
相似
異步串行 —— AsyncSeriesHook
串行
one by one
6.use.js
let {AsyncSeriesHook} = require('tapable') // 解構(gòu)同步勾子
class Lesson {
constructor() {
this.index = 0
this.hooks = {
// 訂閱勾子
arch: new AsyncSeriesHook(['name']),
}
}
start() {
// 發(fā)布
// this.hooks.arch.callAsync('may', function () {
// console.log('end');
// })
// 另一種發(fā)布
this.hooks.arch.promise('may').then(function () {
console.log('end');
}
)
}
tap() { // 注冊監(jiān)聽函數(shù),訂閱
// this.hooks.arch.tapAsync('node', (name, callback) => {
// setTimeout(() => {
// console.log('node', name)
// callback()
// }, 1000)
// })
// this.hooks.arch.tapAsync('react', (name, callback) => {
// setTimeout(() => {
// console.log('react', name)
// callback()
// }, 1000)
// })
// 另一種訂閱
this.hooks.arch.tapPromise('node', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name)
resolve()
}, 1000)
})
})
this.hooks.arch.tapPromise('react', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name)
resolve()
}, 1000)
})
})
}
}
let l = new Lesson()
l.tap(); //注冊兩個函數(shù)
l.start(); // 啟動勾子
6.theory.js
class AsyncSeriesHook { //
constructor(args) { // args => ['name']
this.tasks = []
}
tapAsync(name, task) {
this.tasks.push(task)
}
tapPromise(name, task) {
this.tasks.push(task)
}
callAsync(...args) {
let finalCallback = args.pop()
let index = 0;
let next = () => {
if (this.tasks.length === index) return finalCallback();
let task = this.tasks[index++];
task(...args, next);
}
next();
}
promise(...args) {
// 將promise串聯(lián)起來
let [first, ...other] = this.tasks
return other.reduce((p, n) => {
return p.then(() => n (...args))
}, first(...args))
}
}
let hook = new AsyncSeriesHook(['name'])
// hook.tapAsync('react', function (name, callback) {
// setTimeout(() => {
// console.log('react', name);
// callback()
// }, 1000)
// })
//
// hook.tapAsync('node', function (name, callback) {
// setTimeout(() => {
// console.log('node', name);
// callback()
// }, 1000)
// })
//
// hook.tapAsync('webpack', function (name, callback) {
// setTimeout(() => {
// console.log('webpack', name);
// callback()
// }, 1000)
// })
hook.tapPromise('react', function (name, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve()
}, 1000)
})
})
hook.tapPromise('node', function (name, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve()
}, 1000)
})
})
// hook.callAsync('jw', function () {
// console.log('end');
// })
hook.promise('jw').then(function () {
console.log('end');
})
異步串行 —— AsyncSeriesWaterfallHook
上一個監(jiān)聽函數(shù)的中的callback(err, data)
的第二個參數(shù),可以作為下一個監(jiān)聽函數(shù)的參數(shù)
7.use.js
let {AsyncSeriesWaterfallHook} = require('tapable') // 解構(gòu)同步勾子
class Lesson {
constructor() {
this.index = 0
this.hooks = {
// 訂閱勾子
arch: new AsyncSeriesWaterfallHook(['name']),
}
}
start() {
// 發(fā)布
this.hooks.arch.callAsync('may', function () {
console.log('end');
})
// 另一種發(fā)布
// this.hooks.arch.promise('may').then(function () {
// console.log('end');
// }
// )
}
tap() { // 注冊監(jiān)聽函數(shù),訂閱
this.hooks.arch.tapAsync('node', (name, callback) => {
setTimeout(() => {
console.log('node', name)
// callback(null, 'result')
callback('error', 'result') // 如果放error, 會跳過直接后面的勾子,直接走到最終的
}, 1000)
})
this.hooks.arch.tapAsync('react', (name, callback) => {
setTimeout(() => {
console.log('react', name)
callback()
}, 1000)
})
// 另一種訂閱
// this.hooks.arch.tapPromise('node', (name) => {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// console.log('node', name)
// resolve()
// }, 1000)
// })
// })
// this.hooks.arch.tapPromise('react', (name) => {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// console.log('react', name)
// resolve()
// }, 1000)
// })
// })
}
}
let l = new Lesson()
l.tap(); //注冊兩個函數(shù)
l.start(); // 啟動勾子
7.theory.js
class AsyncSeriesWaterfallHook { //
constructor(args) { // args => ['name']
this.tasks = []
}
tapAsync(name, task) {
this.tasks.push(task)
}
tapPromise(name, task) {
this.tasks.push(task)
}
callAsync(...args) {
let finalCallback = args.pop()
let index = 0;
let next = (err, data) => {
let task = this.tasks[index]
if(!task) return finalCallback();
if (index === 0) {
// 執(zhí)行的第一個
task(...args, next)
} else {
task(data, next)
}
index ++
}
next();
}
promise(...args) {
// 將promise串聯(lián)起來
let [first, ...other] = this.tasks
return other.reduce((p, n) => {
return p.then((data) => n(data))
}, first(...args))
}
}
let hook = new AsyncSeriesWaterfallHook(['name'])
// hook.tapAsync('react', function (name, callback) {
// setTimeout(() => {
// console.log('react', name);
// callback(null, '結(jié)果1')
// }, 1000)
// })
//
// hook.tapAsync('node', function (name, callback) {
// setTimeout(() => {
// console.log('node', name);
// callback(null, '結(jié)果2')
// }, 1000)
// })
//
// hook.tapAsync('webpack', function (name, callback) {
// setTimeout(() => {
// console.log('webpack', name);
// callback()
// }, 1000)
// })
//
hook.tapPromise('react', function (name, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve('result')
}, 1000)
})
})
hook.tapPromise('node', function (name, callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve()
}, 1000)
})
})
//
//
// hook.callAsync('jw', function () {
// console.log('end');
// })
hook.promise('jw').then(function () {
console.log('end');
})
手寫webpack
yarn add webpack webpack-cli -D
webpack.config.js
let path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
npx webpack
生成文件bundle.js
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
"./src/a.js":
(function (module, exports, __webpack_require__) {
eval("let b = __webpack_require__(/*! ./base/b */ \"./src/base/b.js\")\n\nmodule.exports = 'a'+ b\n\n\n\n//# sourceURL=webpack:///./src/a.js?");
}),
"./src/base/b.js":
(function (module, exports) {
eval("module.exports = 'b'\n\n\n//# sourceURL=webpack:///./src/base/b.js?");
}),
"./src/index.js":
(function (module, exports, __webpack_require__) {
eval(" let str = __webpack_require__(/*! ./a.js */ \"./src/a.js\")\n\n console.log(str);\n\n\n//# sourceURL=webpack:///./src/index.js?");
})
});
新建項目用于自己的webpack
,這里叫may-pack
yarn init
如果在node里想執(zhí)行命令掰邢,創(chuàng)建bin
文件,再創(chuàng)建may-pack.js
配置package.json
{
"name": "may-pack",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"bin": {
"may-pack": "./bin/may-pack.js"
}
}
may-pack.js
#! /usr/bin/env node
// node環(huán)境
console.log('start');
運行npm link
將npm 模塊鏈接到對應(yīng)的運行項目中去牺陶,方便地對模塊進行調(diào)試和測試
在想運行may-pack
的項目中運行,npm link may-pack
得到 start
webpack分析及處理
may-pack.js
#! /usr/bin/env node
// node環(huán)境
console.log('start');
let path = require('path')
// 拿到配置文件webpack.config.js
let config = require(path.resolve('webpack.config.js'));
let Compiler = require('../lib/Compiler.js');
let compiler = new Compiler(config);
// 標(biāo)識運行編譯
compiler.run()
創(chuàng)建lib
文件Compiler.js
let path = require('path')
let fs = require('fs')
class Compiler {
constructor(config) {
// entry output
this.config = config
// 需要保存入口文件的路徑
this.entryId = ''; // './src/index.js'
// 需要保存所有的模塊依賴
this.modules = {};
this.entry = config.entry // 入口文件
// 工作目錄
this.root = process.cwd(); // 當(dāng)前運行npx的路徑
}
// 構(gòu)建模塊
buildModule(modulePath, isEntry) {
}
// 發(fā)射文件
emitFile() {
// 用數(shù)據(jù) 渲染想要的
}
run() {
// 執(zhí)行 創(chuàng)建模塊的依賴關(guān)系
this.buildModule(path.resolve(this.root, this.entry), true) // path.resolve(this.root, this.entry) 得到入口文件的絕對路徑
// 發(fā)射打包后的文件
this.emitFile()
}
}
module.exports = Compiler
主要兩個任務(wù)
- 拿到入口Id
- 解析模塊辣之,也就是實現(xiàn)
buildModule
方法
創(chuàng)建依賴關(guān)系
may-pack
中Compiler.js
let path = require('path')
let fs = require('fs')
// babylon 主要把源碼轉(zhuǎn)成ast Babylon 是 Babel 中使用的 JavaScript 解析器掰伸。
// @babel/traverse 對ast解析遍歷語法樹 負責(zé)替換,刪除和添加節(jié)點
// @babel/types 用于AST節(jié)點的Lodash-esque實用程序庫
// @babel/generator 結(jié)果生成
let babylon = require('babylon')
let traverse = require('@babel/traverse').default;
let type = require('@babel/types');
let generator = require('@babel/generator').default
class Compiler {
constructor(config) {
// entry output
this.config = config
// 需要保存入口文件的路徑
this.entryId = ''; // './src/index.js'
// 需要保存所有的模塊依賴
this.modules = {};
this.entry = config.entry // 入口文件
// 工作目錄
this.root = process.cwd(); // 當(dāng)前運行npx的路徑
}
// 拿到模塊內(nèi)容
getSource (modulePath) {
let content = fs.readFileSync(modulePath, 'utf8')
return content
}
parse (source, parentPath) {
console.log(source, parentPath)
}
// 構(gòu)建模塊
buildModule(modulePath, isEntry) {
// 拿到模塊內(nèi)容
let source = this.getSource(modulePath) // 得到入口文件的內(nèi)容
// 模塊id modulePath(需要相對路徑) = modulePath(模塊路徑) - this.root(項目工作路徑) src/index.js
let moduleName = './' + path.relative(this.root, modulePath)
console.log(source, moduleName); // 拿到代碼 和相對路徑 ./src/index.js
if (isEntry) {
this.entryId = moduleName
}
let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName)) // ./src
// 把相對路徑和模塊中的內(nèi)容對應(yīng)起來
this.modules[moduleName] = sourceCode
}
// 發(fā)射文件
emitFile() {
// 用數(shù)據(jù) 渲染想要的
}
run() {
// 執(zhí)行 創(chuàng)建模塊的依賴關(guān)系
this.buildModule(path.resolve(this.root, this.entry), true) // path.resolve(this.root, this.entry) 得到入口文件的絕對路徑
console.log(this.modules, this.entryId);
// 發(fā)射打包后的文件
this.emitFile()
}
}
module.exports = Compiler
ast遞歸解析
parse
方法主要靠解析語法樹來進行轉(zhuǎn)義
babylon
主要把源碼轉(zhuǎn)成ast Babylon 是 Babel 中使用的 JavaScript 解析器召烂。
@babel/traverse
對ast解析遍歷語法樹 負責(zé)替換碱工,刪除和添加節(jié)點
@babel/types
用于AST節(jié)點的Lodash-esque實用程序庫
@babel/generator
結(jié)果生成
yarn add babylon @babel/traverse @babel/types @babel/generator
may-pack
中Compiler.js
let path = require('path')
let fs = require('fs')
// babylon 主要把源碼轉(zhuǎn)成ast Babylon 是 Babel 中使用的 JavaScript 解析器。
// @babel/traverse 對ast解析遍歷語法樹 負責(zé)替換奏夫,刪除和添加節(jié)點
// @babel/types 用于AST節(jié)點的Lodash-esque實用程序庫
// @babel/generator 結(jié)果生成
let babylon = require('babylon')
let traverse = require('@babel/traverse').default;
let type = require('@babel/types');
let generator = require('@babel/generator').default
class Compiler {
constructor(config) {
// entry output
this.config = config
// 需要保存入口文件的路徑
this.entryId = ''; // './src/index.js'
// 需要保存所有的模塊依賴
this.modules = {};
this.entry = config.entry // 入口文件
// 工作目錄
this.root = process.cwd(); // 當(dāng)前運行npx的路徑
}
// 拿到模塊內(nèi)容
getSource (modulePath) {
let content = fs.readFileSync(modulePath, 'utf8')
return content
}
parse (source, parentPath) {
// AST解析語法樹
let ast = babylon.parse(source)
let dependencies = []; // 依賴的數(shù)組
// https://astexplorer.net/
traverse(ast, {
// 調(diào)用表達式
CallExpression(p) {
let node = p.node; //對應(yīng)的節(jié)點
if(node.callee.name === 'require') {
node.callee.name = '__webpack_require__'
let moduledName = node.arguments[0].value // 取到模塊的引用名字
moduledName = moduledName + (path.extname(moduledName) ? '': '.js'); // ./a.js
moduledName = './' + path.join(parentPath, moduledName) // './src/a.js'
dependencies.push(moduledName)
node.arguments = [type.stringLiteral(moduledName)] // 改掉源碼
}
}
})
let sourceCode = generator(ast).code
return { sourceCode, dependencies }
}
// 構(gòu)建模塊
buildModule(modulePath, isEntry) {
// 拿到模塊內(nèi)容
let source = this.getSource(modulePath) // 得到入口文件的內(nèi)容
// 模塊id modulePath(需要相對路徑) = modulePath(模塊路徑) - this.root(項目工作路徑) src/index.js
let moduleName = './' + path.relative(this.root, modulePath)
// console.log(source, moduleName); // 拿到代碼 和相對路徑 ./src/index.js
if (isEntry) {
this.entryId = moduleName
}
// 解析把source源碼進行改造怕篷, 返回一個依賴列表
let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName)) // ./src
// 把相對路徑和模塊中的內(nèi)容對應(yīng)起來
this.modules[moduleName] = sourceCode
dependencies.forEach(dep => { // 附模塊的加載 遞歸加載
this.buildModule(path.join(this.root, dep), false)
})
}
// 發(fā)射文件
emitFile() {
// 用數(shù)據(jù) 渲染想要的
}
run() {
// 執(zhí)行 創(chuàng)建模塊的依賴關(guān)系
this.buildModule(path.resolve(this.root, this.entry), true) // path.resolve(this.root, this.entry) 得到入口文件的絕對路徑
console.log(this.modules, this.entryId);
// 發(fā)射打包后的文件
this.emitFile()
}
}
module.exports = Compiler
生成打包工具
使用ejs模板
may-pack
中main.ejs
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "<%-entryId %>");
})({
<% for(let key in modules){ %>
"<%- key %>":
(function (module, exports,__webpack_require__) {
eval(`<%-modules[key] %>`);
}),
<% } %>
});
yarn add ejs
may-pack
中Compiler.js
let ejs = require('ejs')
// 發(fā)射文件
emitFile() {
// 用數(shù)據(jù) 渲染想要的
// 輸出到那個目錄下
let main = path.join(this.config.output.path, this.config.output.filename)
let templateStr = this.getSource(path.join(__dirname, 'main.ejs'))
let code = ejs.render(templateStr, { entryId: this.entryId, modules: this.modules})
this.assets = {}
// 路徑對應(yīng)的代碼
this.assets[main] = code
fs.writeFileSync(main, this.assets[main])
}
在webpack-training
項目中運行npx may-pack
, 得到bundle.js
,運行得到結(jié)果
增加loader
創(chuàng)建loader
文件夾,創(chuàng)建less-loader1.js
和style-loader1.js
yarn add less
less-loader1.js
// 將less轉(zhuǎn)為css
let less = require('less')
function loader(source) {
let css = ''
less.render(source, function (err, output) {
css = output.css
})
css = css.replace(/\n/g, '\\n');
return css
}
module.exports = loader
style-loader1.js
// 將css插入到html頭部
function loader(source) {
console.log(111);
let style = `
let style = document.createElement('style')
style.innerHTML = ${JSON.stringify(source)}
document.head.appendChild(style)
`
return style
}
module.exports = loader
// JSON.stringify(source) 可以將代碼轉(zhuǎn)為一行
webpack.config.js
let path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.less$/,
use: [
path.resolve(__dirname, 'loader', 'style-loader1'),
path.resolve(__dirname, 'loader', 'less-loader1')
]
}
]
}
}
創(chuàng)建index.less
body {
background: red
}
index.js
let str = require('./a.js')
require('./index.less')
console.log(str);
may-pack
中Compiler.js
// 拿到模塊內(nèi)容
getSource (modulePath) {
// 匹配各種文件的規(guī)則
let rules= this.config.module.rules; // webpack.config.js 中rules的數(shù)組
let content = fs.readFileSync(modulePath, 'utf8')
for (let i = 0; i < rules.length; i++) {
let rule = rules[i]
let {test, use} = rule
let len = use.length - 1
if (test.test(modulePath)) {
// console.log(use[len]);
function normalLoader () {
// console.log(use[len--]);
let loader = require(use[len--])
content = loader(content)
// 遞歸調(diào)用loader 實現(xiàn)轉(zhuǎn)化
if (len >= 0) {
normalLoader()
}
}
normalLoader()
}
}
return content
}
運行npx may-pack
增加plugins
yarn add tapable
may-pack
中Compiler.js
constructor(config) {
// entry output
this.config = config
// 需要保存入口文件的路徑
this.entryId = ''; // './src/index.js'
// 需要保存所有的模塊依賴
this.modules = {};
this.entry = config.entry // 入口文件
// 工作目錄
this.root = process.cwd(); // 當(dāng)前運行npx的路徑
this.hooks = {
entryOption: new SyncHook(), // 入口選項
compile: new SyncHook(), // 編譯
afterCompile: new SyncHook(), // 編譯完成
afterPlugins: new SyncHook(), // 編譯完插件
run: new SyncHook(), // 運行
emit: new SyncHook(), // 發(fā)射
done: new SyncHook() // 完成
}
// 如果傳遞了plugins參數(shù)
let plugins = this.config.plugins
if (Array.isArray(plugins)) {
plugins.forEach(plugin => {
plugin.apply(this); // 這里只是appLy方法不是改變this指向
})
}
this.hooks.afterPlugins.call()
}
在webpack.config.js
中寫插件方法
class P {
apply(compiler) { // 這里只是appLy方法不是改變this指向
// 綁定
compiler.hooks.emit.tap('emit', function () {
console.log('emit');
})
}
}
class P1 {
apply(compiler) { // 這里只是appLy方法不是改變this指向
// 綁定
compiler.hooks.afterPlugins.tap('emit', function () {
console.log('afterPlugins');
})
}
}
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.less$/,
use: [
path.resolve(__dirname, 'loader', 'style-loader'),
path.resolve(__dirname, 'loader', 'less-loader')
]
}
]
},
plugins: [
new P(),
new P1()
]
}
然后在各個地方調(diào)用
may-pack
中may-pack.js
.....
// 調(diào)用
compiler.hooks.entryOption.call()
// 標(biāo)識運行編譯
compiler.run()
may-pack
中Compiler.js
run() {
this.hooks.run.call()
this.hooks.compile.call()
// 執(zhí)行 創(chuàng)建模塊的依賴關(guān)系
this.buildModule(path.resolve(this.root, this.entry), true) // path.resolve(this.root, this.entry) 得到入口文件的絕對路徑
// console.log(this.modules, this.entryId);
this.hooks.afterCompile.call()
// 發(fā)射打包后的文件
this.emitFile()
this.hooks.emit.call()
this.hooks.done.call()
}
運行npx may-pack
loader
webapck.config.js
let path = require('path')
module.exports = {
mode: 'development',
entry: './src/index',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js/,
use: 'loader1' // 如何找到這個loader1
}
]
},
}
創(chuàng)建loader
文件loader1.js
console.log(22);
function loader(source) { // loader的參數(shù)就是源代碼
return source
}
module.exports = loader
webpack.config.js
let path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
resolveLoader: {
// 別名
// alias: {
// loader1: path.resolve(__dirname, 'loader', 'loader1')
// }
modules: ['node_modules', path.resolve(__dirname, 'loader')] // 先找node_modules, 再去loader中去找
},
module: {
rules: [
{
test: /\.js$/,
// use: [path.resolve(__dirname, 'loader', 'loader1')]
use: 'loader1' // 如何找到這個loader1
},
// {
// test: /\.less$/,
// use: [
// path.resolve(__dirname, 'loader', 'style-loader'),
// path.resolve(__dirname, 'loader', 'less-loader')
// ]
// }
]
},
}
如何找到這個loader1
- 通過配別名
alias
- 通過
modules
npx webpack
配置多個loader
- 數(shù)組方式
先分別在loader
文件下創(chuàng)建酗昼,loader2.js
和loader3.js
function loader(source) { // loader的參數(shù)就是源代碼
console.log('loader2'); // loader3.js 類似
return source
}
module.exports = loader
webpack.config.js
rules: [
{
test: /\.js$/,
use: ['loader3', 'loader2', 'loader1']
},
]
運行npx webpack
,分別打出
loader1
loader2
loader3
- 對象方式
rules: [
{
test: /\.js$/,
use: ['loader3']
},
{
test: /\.js$/,
use: ['loader2']
},
{
test: /\.js$/,
use: ['loader1']
}
]
運行npx webpack
,分別打出
loader1
loader2
loader3
loader
的順序: 從右到左, 從下到上
也可以通過配置不同的參數(shù)改變loader
的執(zhí)行順序廊谓,pre
前面的, post
在后面的麻削, normal
正常
{
test: /\.js$/,
use: ['loader1'],
enforce: "pre"
},
{
test: /\.js$/,
use: ['loader2']
},
{
test: /\.js$/,
use: ['loader3'],
enforce: "post"
},
loader
帶參數(shù)執(zhí)行的順序: pre -> normal -> inline -> post
inline
為行內(nèi)loader
在loader
文件中新建inlin-loader
function loader(source) { // loader的參數(shù)就是源代碼
console.log('inline');
return source
}
module.exports = loader
src/a.js
module.exports = 'may'
src/index
console.log('hello')
let srt = require('-!inline-loader!./a')
-
-!
禁用pre-loader
和normal-loader
來處理了
loader1
loader2
loader3
inline
loader3
-
!
禁用normal-loader
loader1
loader2
loader3
loader1
inline
loader3
-
!!
禁用pre-loader
蒸痹、normal-loader
、post-loader
,只能行內(nèi)處理
loader1
loader2
loader3
inline
loader 默認由兩部分組成pitch
和normal
user: [loader3, loader2, loader1]
無返回值: 先執(zhí)行pitch方法,從左到右呛哟,再獲取資源
pitch loader - 無返回值
pitch loader3 → loader2 → loader1
↘
資源
↙
normal loader3 ← loader2 ← loader1
有返回值: 直接跳過后續(xù)所有的loader
包括自己的,跳到之前的loader
, 可用于阻斷
user: [loader3, loader2, loader1]
pitch loader - 有返回值
pitch loader3 → loader2 loader1
↙
有返回值 資源
↙
normal loader3 loader2 loader1
loadeer2.js
function loader(source) { // loader的參數(shù)就是源代碼
console.log('loader2');
return source
}
loader.pitch = function () {
return '111'
}
module.exports = loader
結(jié)果
loader3
babel-loader
實現(xiàn)
yarn add @babel/core @babel/preset-env
webpack.config.js
{
test: '\.js$/',
use: {
loader: 'babel-loader2',
options: {
presets: [
'@babel/preset-env'
]
}
}
}
在loader
文件創(chuàng)建babel-loader2.js
(如果你已經(jīng)裝過babel-loader
)
拿到babel
的參數(shù)
yarn add loader-utils
// 需要在webpack.config.js拿到babel的預(yù)設(shè), 通過預(yù)設(shè)轉(zhuǎn)換模塊, 先引入babel
let babel = require('@babel/core')
// 拿到babel的參數(shù) 需要工具 loaderUtils
let loaderUtils =require('loader-utils')
function loader(source) { // loader的參數(shù)就是源代碼 這里的this就是loader的上下文
let options = loaderUtils.getOptions(this)
console.log(this.resourcePath, 444); // [./src/index.js]
let callback = this.async(); // babel的轉(zhuǎn)換是異步的,同步的返回是不行的叠荠, 不能用return 同步就是直接掉用 異步會在async中
babel.transform(source, {
...options,
sourceMap: true, // 是否設(shè)置sourceMap 還需要再webpack.config.js 中配置 devtool: 'source-map'
filename: this.resourcePath.split('/').pop() // 給生成的`source-map`指定名字
}, function (err, result) {
callback(err, result.code, result.map) // 異步 參數(shù)分別是「錯誤 轉(zhuǎn)化后的代碼 和 sourceMap」
})
console.log(options);
// return source 失效
}
module.exports = loader
index.js
class May {
constructor () {
this.name = 'may'
}
getName () {
return this.name
}
}
let may = new May()
console.log(may.getName());
npx webpack
banner-loader
實現(xiàn)(自創(chuàng))
給所有匹配的js
加一個注釋
webpack.config.js
{ // 給所有匹配的`js`加一個注釋
test: /\.js$/,
use: {
loader: 'banner-loader',
options: {
text: 'may',
filename: path.resolve(__dirname, 'banner.js')
}
}
}
banner.js
二次星球中毒
在loader
文件創(chuàng)建banner-loader.js
yarn add schema-utils
校驗自己寫的loader
格式是否正確
banner-loader.js
// 拿到loader的配置
let loaderUtils = require('loader-utils')
// 校驗loader
let validateOptions = require('schema-utils')
// 讀取文件
let fs = require('fs') // 異步
function loader(source) { // loader的參數(shù)就是源代碼
let options = loaderUtils.getOptions(this)
let callback = this.async() // 讀取文件是異步
let schema = {
type: 'object',
properties: {
text: {
type: 'string'
},
filename: {
type: 'string'
}
}
}
validateOptions(schema, options, 'banner-loader') // 自己的校驗格式, 自己的寫的配置扫责, 對應(yīng)的loader名字
if (options.filename) {
this.cacheable(false) // 不要緩存 如果有大量計算 推薦緩存
// this.cacheable && this.cacheable()
this.addDependency(options.filename) // 自動增加依賴
fs.readFile(options.filename, 'utf8', function (err, data) {
callback(err, `/**${data}**/${source}`)
})
} else {
callback(null, `/**${options.text}**/${source}`)
}
return source
}
module.exports = loader
優(yōu)化:
- 修改
banner.js
的內(nèi)容后,webpack
進行監(jiān)控榛鼎,打包webapck.config.js
配置watch: true
-
loader
緩存
實現(xiàn)file-loader
和url-loader
yarn add mime
其主要用途是設(shè)置某種擴展名的文件的響應(yīng)程序類型
創(chuàng)建file-loader.js1
// 拿到babel的參數(shù) 需要工具 loaderUtils
let loaderUtils = require('loader-utils')
function loader(source) { // loader的參數(shù)就是源代碼
// file-loader需要返回路徑
let filename = loaderUtils.interpolateName(this, '[hash].[ext]', {content: source })
this.emitFile(filename, source) // 發(fā)射文件
console.log('loader1');
return `module.exports="${filename}"`
}
loader.raw = true // 二進制
module.exports = loader
創(chuàng)建url-loader1.js
// 拿到babel的參數(shù) 需要工具 loaderUtils
let loaderUtils = require('loader-utils')
let mime = require('mime') // 途是設(shè)置某種擴展名的文件的響應(yīng)程序類型
function loader(source) { // loader的參數(shù)就是源代碼
let {limit} = loaderUtils.getOptions(this)
console.log(this.resourcePath);
if (limit && limit > source.length) {
return `module.exports="data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"`
} else {
return require('./file-loader1').call(this, source)
}
}
loader.raw = true // 二進制
module.exports = loader
webpack.config.js
{
test: /\.png$/,
// 目的是根據(jù)圖片生成md5 發(fā)射到dist目錄下,file-loader 返回當(dāng)前圖片路徑
// use: 'file-loader'
// 處理路徑
use: {
loader: 'url-loader1',
options: {
limit: 200 * 1024
}
}
}
index.js
引入圖片
import p from './photo.png'
let img = document.createElement('img')
img.src = p
document.body.appendChild(img);
less-loader
和css-loader
先安裝less
分別創(chuàng)建style-loader2
css-loader2
less-loader2
style-loader1
與 less-loader1
同之前的
css-loader
主要用來處理css
中的圖片鏈接鳖孤,需要把url
轉(zhuǎn)換成require
webpack.config.js
{
test: /\.png$/,
// 目的是根據(jù)圖片生成md5 發(fā)射到dist目錄下者娱,file-loader 返回當(dāng)前圖片路徑
// use: 'file-loader'
// 處理路徑
use: {
loader: 'url-loader1',
options: {
limit: 200 * 1024
}
}
},
{
test: /\.less$/,
use: ['style-loader2', 'css-loader2', 'less-loader2']
}
創(chuàng)建index.less
@base: #f938ab;
body {
background: @base;
background: url("./photo.png");
}
less-loader2.js
// 將less轉(zhuǎn)為css
let less = require('less')
function loader(source) {
let css = ''
// console.log(source, 2222);
less.render(source, function (err, output) {
// console.log(output);
css = output.css
})
// css = css.replace(/\n/g, '\\n');
return css
}
module.exports = loader
css-loader2.js
// css-loader 用來解析@import這種語法,包括css中引入的圖片
function loader(source) {
let reg = /url\((.+?)\)/g // 匹配括號
let pos = 0;
let current;
let arr = ['let list = []']
while (current = reg.exec(source)) {
let [matchUrl, g] = current // matchUrl -> 'url("./photo.png")', g -> '"./photo.png"'
// console.log(matchUrl, g, 88);
let lastIndex = reg.lastIndex - matchUrl.length // 拿到css從開通到地址鏈接之前的index
arr.push(`list.push(${JSON.stringify(source.slice(pos, lastIndex))})`) // 拼入開始和地址之前的代碼
pos = reg.lastIndex
arr.push(`list.push('url('+ require(${g}) +')')`) // 拼入圖片地址
}
arr.push(`list.push(${JSON.stringify(source.slice(pos))})`) // 拼入地址到結(jié)尾的代碼
arr.push(`module.exports = list.join('')`)
console.log(arr.join('\r\n'));
// let list = []
// list.push("body {\\n background: #f938ab;\\n background: ")
// list.push('url('+ require("./photo.png") +')')
// list.push(";\\n}\\n")
// module.exports = list.join('')
return arr.join('\r\n')
}
module.exports = loader
style-loader2.js
let loaderUtils = require('loader-utils')
// 將css插入到html頭部
function loader(source) {
let str = `
let style = document.createElement('style')
style.innerHTML = ${JSON.stringify(source)}
document.head.appendChild(style)
`
return str
}
// style-loader寫了pitch,有返回后面的跳過,自己的寫不會走
loader.pitch = function (remainingRequest) { // 剩余的請求
console.log(loaderUtils.stringifyRequest(this, '!!' + remainingRequest, 99999999))
// 讓style-loader 處理 less-loader 和css-loader拼接的結(jié)果
// 得到 /Users/liuhuimin/work/webpack/loader/css-loader2.js!/Users/liuhuimin/work/webpack/loader/less-loader2.js!/Users/liuhuimin/work/webpack/src/index.less
// 剩余的請求 less-loader!css-loader!./index.less
// console.log(remainingRequest, 1223);
// require返回的就是css-loader處理好的結(jié)果require('!!css-loader!less-loader!./index.less')
let str = `
let style = document.createElement('style')
style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)})
document.head.appendChild(style)
`
// stringifyRequest 絕對路徑轉(zhuǎn)相對路徑
return str
}
module.exports = loader
user: ['style-loader2', 'css-loader2', 'less-loader2']
pitch loader - 有返回值
pitch style-loader2 → css-loader2 less-loader2
↙
有返回值 資源
↙
normal style-loader2 css-loader2 less-loader2
在style-loader2
中 引用了less-loader
css-loader
和less
文件
webpack 中的插件
yarn add webpack webpack-cil -D
webpack.config.js
let path = require('path')
let DonePlugin = require('./plugins/DonePlugins')
let AsyncPlugins = require('./plugins/AsyncPlugins')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new DonePlugin(), // 同步
new AsyncPlugins() // 異步
]
}
node_modules/webpack/lib
中查看Compiler.js
- 同步
plugins/DonePlugins
打包完成
class DonePlugins {
apply (compiler) {
console.log(1);
compiler.hooks.done.tap('DonePlugin', (stats) => {
console.log('編譯完成');
})
}
}
module.exports = DonePlugins
- 異步
plugins/AsyncPlugins
class AsyncPlugins {
apply (compiler) {
console.log(2);
compiler.hooks.emit.tapAsync('AsyncPlugin', (complete, callback) => {
setTimeout(() => {
console.log('文件發(fā)射出來');
callback()
}, 1000)
})
compiler.hooks.emit.tapPromise('AsyncPlugin', (complete, callback) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('文件發(fā)射出來 222');
resolve()
}, 1000)
})
})
}
}
module.exports = AsyncPlugins
文件列表插件
希望生成一個文件描述打包出來的文件
在plugins
中新建FileListPlugin
class FileListPlugin {
constructor ({filename}) {
this.filename = filename
}
apply (compiler) {
// 文件已經(jīng)準(zhǔn)備好了 要進行發(fā)射
// emit
compiler.hooks.emit.tap('FileListPlugin', (compilation) => {
let assets = compilation.assets;
console.log(assets, 55);
let content = `## 文件名 資源大小\r\n`
// [ [bundls.js, {}], [index.html, {}]]
Object.entries(assets).forEach(([filename, stateObj]) => {
content += `- ${filename} ${stateObj.size()}\r\n`
})
// 資源對象
assets[this.filename] = {
source () {
return content;
},
size () {
return content.length
}
}
})
}
}
module.exports = FileListPlugin
let path = require('path')
let DonePlugin = require('./plugins/DonePlugins')
let AsyncPlugins = require('./plugins/AsyncPlugins')
let HtmlWebpackPlugin = require('html-webpack-plugin')
let FileListPlugin = require('./plugins/FileListPlugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new DonePlugin(),
new AsyncPlugins(),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new FileListPlugin({
filename: 'list.md'
})
]
}
生成list.md
內(nèi)聯(lián)的webpack
插件
新建index.css
引入index.js
yarn add css-loader mini-css-extract-plugin -D
希望打包后css苏揣、js
內(nèi)聯(lián)在index.html
文件中
創(chuàng)建plugins
中InlineSourcePlugins.js
yarn add --dev html-webpack-plugin@next
webpack.config.js
let path = require('path')
let DonePlugin = require('./plugins/DonePlugins')
let AsyncPlugins = require('./plugins/AsyncPlugins')
let HtmlWebpackPlugin = require('html-webpack-plugin')
let FileListPlugin = require('./plugins/FileListPlugin')
let InlineSourcePlugins = require('./plugins/InlineSourcePlugins')
let MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
// new DonePlugin(),
// new AsyncPlugins(),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin({
filename: 'index.css'
}),
new InlineSourcePlugins({
match: /\.(js|css)/
}),
// new FileListPlugin({
// filename: 'list.md'
// })
]
}
InlineSourcePlugins.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 把外鏈的標(biāo)簽編程內(nèi)聯(lián)的標(biāo)簽
class InlineSourcePlugins {
constructor({match}) {
this.reg = match // 正則
}
// 處理某一個標(biāo)簽
processTag(tag, compilation) {
let newTag = {}
let url = ''
if (tag.tagName === 'link' && this.reg.test(tag.attributes.href)) {
newTag = {
tagName: 'style',
attributes: {type: 'text/css'}
}
url = tag.attributes.href
} else if (tag.tagName === 'script' && this.reg.test(tag.attributes.src)) {
newTag = {
tagName: 'script',
attributes: {type: 'application/javascript'}
}
url = tag.attributes.src
}
if (url) {
newTag.innerHTML = compilation.assets[url].source(); // 文件內(nèi)容放到innerHTML屬性中
delete compilation.assets[url] // 刪除原有的資源
return newTag
// console.log(compilation.assets[url].source());
}
return tag
}
// 處理引入標(biāo)簽的數(shù)據(jù)
processTags(data, compilation) {
let headTags = []
let bodyTags = []
data.headTags.forEach(headTag => {
headTags.push(this.processTag(headTag, compilation))
})
data.bodyTags.forEach(bodyTag => {
bodyTags.push(this.processTag(bodyTag, compilation))
})
console.log({...data, headTags, bodyTags})
return {...data, headTags, bodyTags}
}
apply(compiler) {
// 通過webpackPlugin來實現(xiàn) npm搜索 html-webpack-plugin
compiler.hooks.compilation.tap('InlineSourcePlugins', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
'alertPlugin',
(data, callback) => {
// console.log('======');
// console.log(data) // 插入html標(biāo)簽的數(shù)據(jù)
// console.log('======');
data = this.processTags(data, compilation) // compilation.assets 資源的鏈接
callback(null, data)
})
})
}
}
module.exports = InlineSourcePlugins
打包后自動發(fā)布
打包好的文件自動上傳致七牛
需要這幾個參數(shù)
bucket: '' // 七牛的存儲空間
domain: '',
accessKey: '', // 七牛云的兩對密匙
secretKey: '' // 七牛云的兩對密匙
注冊七牛黄鳍,并在對象存儲里面,新建存儲空間列表test
,bucket: 'test'
內(nèi)容管理外鏈接默認域名 domain: 'xxxxxxxx'
右上角個人面板里面?zhèn)€人中心,密鑰管理分別對應(yīng)accessKey
和secretKey
進入開發(fā)者中心 -> SDK&工具 -> 官方SDK -> Node服務(wù)端文檔 —> 文件上傳
npm install qiniu
webpack.config.js
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin({
filename: 'index.css'
}),
new UploadPlugin({
bucket: 'test', // 七牛的存儲空間
domain: 'poyrjyh1b.bkt.clouddn.com',
accessKey: 'xxxxxx', // 七牛云的兩對密匙
secretKey: 'yyyyyy' // 七牛云的兩對密匙
})
]
UploadPlugin.js
let qiniu = require('qiniu')
let path = require('path')
class UploadPlugin {
constructor (options = {}) {
// 參考 https://developer.qiniu.com/kodo/sdk/1289/nodejs
let { bucket = '', domain = '', accessKey = '', secretKey = ''} = options
let mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
let putPolicy = new qiniu.rs.PutPolicy({
scope: bucket
});
this.uploadToken = putPolicy.uploadToken(mac)
let config = new qiniu.conf.Config();
this.formUploader = new qiniu.form_up.FormUploader(config)
this.putExtra = new qiniu.form_up.PutExtra()
}
apply (compiler) {
compiler.hooks.afterEmit.tapPromise('UploadPlugin', (complication) => {
let assets = complication.assets
let promise = []
Object.keys(assets).forEach(filename => {
promise.push(this.upload(filename))
})
return Promise.all(promise)
})
}
upload (filename) {
return new Promise((resolve, reject) => {
let localFile = path.resolve(__dirname, '../dist', filename)
this.formUploader.putFile(this.uploadToken, filename, localFile, this.putExtra, function(respErr,
respBody, respInfo) {
if (respErr) {
reject(respErr)
}
if (respInfo.statusCode == 200) {
resolve(respBody)
} else {
console.log(respInfo.statusCode)
console.log(respBody)
}
});
})
}
}
module.exports = UploadPlugin