webpack5.0
简介:webpack是一个基于node.js的模块打包工具
安装/卸载
初始化目录(创建一个node的包文件)
npm init //npm init -y 直接跳过询问
全局安装
cnpm install webpack webpack-cli -g
查看是否安装成功
webpack -v
局部安装
cnpm install webpack webpack-cli --save-dev
查看是否安装成功
npx webpack -v
或者
./node_modules/.bin/webpack -v
安装指定版本的webpack
cnpm install webpack@4.16.5 webpack-cli --save-dev
卸载全局webpack
npm uninstall webpack webpack-cli -g
webpack 4+ 版本,需要安装 CLI
webpack-cli 作用:可以在命令行运行webpack。
建议局部安装webpack,这样的话可以启动不同版本的webpack。
配置脚本
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
},
"author": "",
"license": "ISC"
}
运行方式
webpack ./index.js //全局安装webpack 局部 ./node_modules/.bin/webpack ./index.js
npx webpack ./index.js //局部安装webpack
npm run build ./index.js //脚本形式(没有配置webpack.config)
配置webpack.config.js
const path = require("path")
module.exports = {
entry: "./index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist")
}
}
//__dirname webpack.config.js 所在目录的路径
运行
npx webpack
npx webpack --config a.js //以a.js为配置文件运行 npx webpack
loaders
webpack不能识别非js结尾的文件,loader是一种打包方案,定义不同的文件应该如何打包。
常用loader
文件图片字体的相关loader(file-loader、url-loader)
file-loader
作用:用来处理图片字体等文件
原理:将文件移动到打包后的文件夹(dist)中,并返回文件名称。
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: "file-loader",
options: {
name: "[name].[ext]",
},
},
},
],
},
rules的参数
属性 | 说明 | 注意事项 |
---|---|---|
test | 匹配规则 | Rule.resource.test 的简写。如果你提供了一个 Rule.test 选项,就不能再提供 Rule.resource。 |
use | 使用的loader | 传递字符串(如:use: [ “style-loader” ])是 loader 属性的简写方式(如:use: [ { loader: "style-loader "} ])。 |
loader | 匹配规则 | Rule.use: [ { loader } ] 的简写。。 |
loaders | 匹配规则 | Rule.use的别名 |
options的参数
参数名 | 参数描述 | 使用示例 |
---|---|---|
name | 文件名 | name:"[name][hash:5][path].[ext]" |
publicPath | 公共路径 | 如果使用cdn时可以使用 |
outputPath | 输出目录 | 其实可以在name参数中使用"/image/[name].[ext]"取代替 |
context | 文件上下文 | 影响上面的path作用不大,默认webpack.config.js |
plceholder
参数名 | 参数描述 | 默认值 |
---|---|---|
[ext] | 资源扩展名 | file.extname |
[name] | 资源的基本名称 | file.basename |
[path] | 资源相对于 context的路径 | file.dirname |
[hash] | 内容的哈希值 hashType : hash : digestType : length | md5 |
file-loader字体文件的处理
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
url-loader
url loader依赖file-loader 增加limit参数 用来限制图片的打包方式
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 1024*5,
name:'img/[name].[hash:7].[ext]'
}
},
]
},
}
webapck5 内置了assetModule模块
asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type:"asset",
generator: {
filename: '[name][hash:3][ext][query]'
},
parser: {
dataUrlCondition: {
maxSize: 200 * 1024
}
}
},
],
},
处理样式loader (css-loader、style-loader)
css-loader
作用:分析css文件的依赖关系,合并css(支持@import url())
{
test:/\.scss$/,
use:["style-loader",{
loader:"css-loader",
options: {
importLoaders: 2,
},
},"postcss-loader","sass-loader"]//注意顺序
}
名称 | 默认值 | 描述 |
---|---|---|
modules | false | 启用/禁用 CSS 模块 |
importLoaders | 0 | 使用@import语法,前应用的 loader 的数量(在css-loader之前还要运行多少loader) |
{
test: /\.css(\?.*)?$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
// importLoaders: 1, 这样可以在@import引入的的文件中 不会走postcss-loader
},
},
"postcss-loader",
],
},
style-loader
将css插入HTML中
sass-loader
没什么好说的 处理sass文件,注意elementUI使用的是node-sass,如果使用dart-sass可能导致icon图标显示乱码
less-loader
处理sass文件
postcss-loader
使用autoprefixer增加厂商前缀
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"]
}
]
}
}
新增postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
浏览器列表配置一下( 保证能显示前缀,此处有坑,在webpack5中有browserslist会导致devserve不能正常刷新)
"browserslist": [
"iOS >= 6",
"Android >= 4",
"IE >= 9"
],
js相关的配置
1.安装babel-loader babel/core (babel/core主要都是一些去对代码进行转换的核心方法)
npm install --save-dev babel-loader @babel/core
配置规则
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, //忽略的模块
loader: "babel-loader"
}
]
}
3.设置预设 安装@babel/preset-env 添加babel.config.json
npm install @babel/preset-env --save-dev
{
"presets": ["@babel/preset-env"]
}
babel-loader 负责解析规则
babel-core 对代码进行转换的核心方法
babel/preset-env 翻译ES6(并不会翻译新增的API如promise)
polyfill 对新api的扩展如可以使用新的内置函数,如Promise或WeakMap,静态方法如Array.from或Object.assign
polyfill
import '@babel/polyfill'
// 新增API
new Promise(function () {});
//-----------------
require("@babel/polyfill");
// 新增API
new Promise(function () {});
小细节:import被编译成了require,如果想要编译出来的模块引入规范还是import,则可以在preset-env的配置项中添加"modules": false即可。
modules的options:“amd” | “umd” | “systemjs” | “commonjs” | “cjs” | “auto” | false,默认为"auto"
按需引入
/* babel.config.js */
module.exports = {
presets: [
[
"@babel/preset-env", {
"modules": false,
"useBuiltIns": "entry",
'targets': {
'browsers': ['ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS浏览器版本7
}
}
]
],
plugins: [
]
}
useBuiltIns
选项:“usage”| “entry”| false,默认为false。
entry是在入口处将根据我们配置的浏览器兼容,将目标浏览器环境所有不支持的API都引入。
usage就很nb了,当配置成usage的时候,babel会扫描你的每个文件,然后检查你都用到了哪些新的API,跟进我们配置的浏览器兼容,只引入相应API的polyfill
/* babel.config.js */
module.exports = {
presets: [
[
"@babel/preset-env", {
"modules": false,
"useBuiltIns": "entry",
"corejs": "3",
'targets': {
'browsers': ['not ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS浏览器版本7
}
}
]
],
plugins: [
]
}
@babel/runtime(依赖@babel/helpers和regenerator-runtime)#
有的时候一些语法的转换会比较复杂,babel会引入一些helper函数,比如说对es6的class进行转换
/* study.js */
class Test {}
/* study-compiled.js */
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Test = function Test() {
_classCallCheck(this, Test);
};
可以看到上面引入了helper函数来处理class的转换。但是问题又来了,如果好多文件都使用到了复杂语法的转换,这个还是简单点的,有些helper函数是很复杂代码量很多的,那岂不是每个文件都会定义一遍这些个函数,每个文件的代码会很多?如果说可以把这些helper函数都抽离到一个公共的包里,用到的地方只需要引入对应的函数即可,我们的编译出来的代码量会大大滴减少,这个时候就需要用到 @babel/plugin-transform-runtime 插件来配合@babel/runtime进行使用。记得先安装一波,然后在插件选项中加入 @babel/plugin-transform-runtime 这个插件,然后我们来看看编译后的效果:
在js中直接引入
import "@babel/polyfill";
会造成打包体积过大
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage"
}
]
]
}
使用useBuiltIns可以节省打包体积(不用再在js中引入@babel/polyfill)(应该会造成全局污染)
npm install --save-dev @babel/plugin-transform-runtime
{
"plugins": ["@babel/plugin-transform-runtime"]
}
默认配置
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
}
/* babel.config.js */
module.exports = {
presets: [
[
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "usage",
"corejs": "3",
'targets': {
'browsers': ["ie >= 8", "iOS 7"] // 支持ie8,直接使用iOS浏览器版本7
}
}
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2
}
]
]
}
corejs 选项 选项 | 安装命令 |
---|---|
false | npm install --save @babel/runtime |
2 | npm install --save @babel/runtime-corejs2 |
3 | npm install --save @babel/runtime-corejs3 |
corejs: 2仅支持全局变量(例如Promise)和静态属性(例如Array.from),corejs: 3还支持实例属性(例如[].includes)
react 相关的配置
{
"presets": [
["@babel/preset-env",{
"useBuiltIns":"usage",
"corejs":3
}],
"@babel/preset-react"
]
}
import React,{Component} from "react";
import ReactDom from "react-dom"
class App extends Component{
render(){
return <div> 你好 周杰伦</div>
}
}
ReactDom.render(<App/>,document.getElementById("app"))
plugins 插件
插件就是在webpack的指定时间去做只能定的操作,类似于生命周期
常用插件
html-webpck-plugin(生成html文件,自动引入js)
详细文档:html-webpck-plugin文档
常用参数
参数 | 默认值 | 使用说明 |
---|---|---|
tittle | 标题 | <%= htmlWebpackPlugin.options.title %> |
filename | 文件名 | ‘index.html’ |
template | 模板 | “./src/index.html” |
plugins:[
new HtmlWebpackPlugin({
template:"./src/index.html"
})
]
clean-webpack-plugin
webpck5 已经不太需要
output: {
clean: true, // Clean the output directory before emit.
}
module.exports = {
output: {
environment: {
arrowFunction: true,// webpack5 默认支持肩头函数,有点坑,为支持IE,这个需要关闭
// The environment supports BigInt as literal (123n).
bigIntLiteral: false,
// The environment supports const and let for variable declarations.
const: true,
// The environment supports destructuring ('{ a, b } = obj').
destructuring: true,
// The environment supports an async import() function to import EcmaScript modules.
dynamicImport: false,
// The environment supports 'for of' iteration ('for (const x of array) { ... }').
forOf: true,
// The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
module: false,
},
},
};
splitChunksPlugin (代码分割相关)
Entry Output(入口出口)
配置多入口
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
配置cdn
output: {
filename: '[name].js',
path: __dirname + '/dist'
publicPath: "http://cdn.example.com",
clean: true,
assetModuleFilename :'[hash][ext][query]'
}
sourcemap(源映射)
devtool: 'inline-source-map'
映射打包过后的文件和源文件
iniline 不生成sourcemap
cheap 只关注业务代码和行
module 关注第三方包,loaders
开发:
cheap-module-eval-source-map
生产
cheap-moudle-source-map
开发相关
使用观察模式
"scripts": {
"watch": "webpack --watch",
"build": "webpack"
},
运行npm run watch
不要使用cleanwebpackplugin
使用webpack-dev-server
安装
cnpm install --save-dev webpack-dev-server
配置webpack.config.js
devServer: {
contentBase: './dist'
}
添加 package.json
"dev": "webpack serve",
proxy: {//高版本的是"proxy"
// 当你请求是以/api开头的接口,则我帮你代理访问到https://api.douban.com
'/api/*': {
target: 'https://api.douban.com', // 你接口的域名
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
pathRewrite: {
'^/api/old-path' : '/api/new-path', // 重写请求,比如我们源访问的是api/old-path,那么请求会被解析为/api/new-path
'^/api/remove/path' : '/path' // 同上
}
} }
HMR(热更新)
允许运行时更新各个模块,而是全局刷新
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin")
const {CleanWebpackPlugin} = require("clean-webpack-plugin")
const webpack = require("webpack")
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
},
mode: "development",
devServer:{
contentBase:'./dist',
+ hot:true,
// hotOnly:true//就是不刷新页面
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: "url-loader",
options: {
limit: 200,
name: "img/[name]_[hash:5].[ext]",
},
},
],
},
{
test: /\.css$/,
use: ["style-loader", {
loader: "css-loader", // 将 CSS 转化成 CommonJS 模块
// options:{
// importLoaders:1
// }
options: {
importLoaders: 1 // 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, sass-loader
}
},"postcss-loader"],
},
{
test: /\.scss$/,
use: [{
loader: "style-loader" // 将 JS 字符串生成为 style 节点
}, {
loader: "css-loader", // 将 CSS 转化成 CommonJS 模块
options: {
importLoaders: 2 // 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, sass-loader
}
}, {
loader: "sass-loader" // 将 Sass 编译成 CSS
},"postcss-loader"]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: {
loader: "file-loader",
options: {
name: "font/[name].[ext]",
}
},
},
],
},
plugins:[
new HtmlWebpackPlugin({
template:"./src/index.html"
}),
+ new webpack.HotModuleReplacementPlugin()//webpack5中会自动引入
// new CleanWebpackPlugin()
]
};
js 中需要额外处理(css-loader,vue-loader帮助我们底层实现)
if(module.hot){
module.hot.accept("./aside.js",()=>{//如果启用HMR,当./aside.js发生变化
document.getElementById("app").removeChild(document.getElementById("btn"))
console.log(123)
new Aside()
})
}
tree shaking
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'development',
optimization: {
usedExports: true,
},
};
配置sideEffects
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}
codeSplitting(代码分割)
第三方库 可以用户缓存起来 用户只用加载业务逻辑代码就可以
同步代码配置
optimization: {
usedExports: true,
splitChunks: {
chunks: 'all',
},
},
异步代码无需配置会自动分割,建议进行配置,实现按需加载
splitchunksplugin
optimization: {
splitChunks: {
chunks: "all",//"all" 所有 “initial” “async”
// minSize: 20000,//引入包代码分割的大小,小于进行打包
// maxSize:200,
minRemainingSize: 0,
minChunks: 1,//chunks入口文件至少引入多少次进行代码分割
maxAsyncRequests: 30,//按需加载时的最大并行请求数。
maxInitialRequests: 30,//入口文件代码分割
enforceSizeThreshold: 50000,
cacheGroups: { //缓存组 多个会打包近一个组
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -30,
reuseExistingChunk: true,
// filename:"[name].js" // 打包文件名称
},
default: {
minChunks: 1,
priority: -20,
reuseExistingChunk: true,//是否复用被打包的模块
// filename:"common.js"
},
},
},
},
css 文件的代码分割
lazyload
webpack和缓存(caching)
//contenthash
output: {
filename: "[name][contenthash].js",
path: path.resolve(__dirname, "dist"),
clean: true, //清除打包目录
assetModuleFilename: "images/[hash][ext]",
environment: {
arrowFunction: false
},
chunkFilename:'[name].chunk.js'
},
shiming
new webpack.ProvidePlugin({
$:'jquery'
})
//$("app").css({}) 自动引入jquery
importsloader
loade:“imports-loader?this=>window
环境变量
Library
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
library: {
name: 'webpackNumbers',//script 标签引入
type: 'umd',//cmd amd esmodule 通用
}
},
};
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_',
},
这意味着你的 library 需要一个名为 lodash 的依赖,这个依赖在 consumer 环境中必须存在且可用。
PWA
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management',
title: 'Progressive Web Application',
}),
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};```
```javascript
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
性能优化 多页面打包
编写loder
loader 本质上是导出为函数的 JavaScript 模块,不要使用箭头函数,
自己编写一个将es6代码转换成ES5
cnpm install @babel/parser @babel/core @babel/preset-env
const parser = require("@babel/parser");
const babel = require("@babel/core");
module.exports = function (source) {
console.log(source, 555);
console.log(this.query, 666);
const ast = parser.parse(source, {
//表示我们要解析的是ES模块
sourceType: "module",
});
console.log(ast, 777);
let { code } = babel.transformFromAst(ast, null, {
//将抽象语法树转换成可以执行的代码
presets: ["@babel/preset-env"],
});
console.log(code, 888);
//this.callback(null,code,) 第三个参数是sourcemap
const callback = this.async();
setTimeout(() => {
code = code.replace("gaojian", this.query.name);
callback(null, code);
}, 3000);
return code;
};
mode: "development",
// resolveLoader:{
// modules:["node_modules","./loader"]
// },
module: {
rules: [
{
test: /\.js$/,
use: {
loader: path.resolve(__dirname, "./loader/minibabel-loader.js"),
// loader: "minibabel-loader",
options: {
name: "孙悟空",
},
},
},
],
},
- 自己编写的loader要使用path.resolve(__dirname, “./path”) 如果想直接用名称 需要配置resolveLoader
- this.query 可以获取options里面传递的参数
- this.callback 可以传递多个参数出去
- this.async 可以处理异步操作
loaderloader文档
编写plugin
插件能够 hook 到每一个编译(compilation)中发出的关键事件中。 在编译的每个阶段中,插件都拥有对 compiler 对象的完全访问能力, 并且在合适的时机,还可以访问当前的 compilation 对象
编写一个将一个文件放在dist 目录中的插件
plugin文档
const path = require("path");
const fs = require("fs")
class CopyWebpackPlugin {
constructor(options) {
console.log(options);
this.options = options;
}
apply(compiler) {
console.log(compiler.hooks,888);
//compiler webpack实例 webapck对象 emit hook 将文件放入dist目录
//同步hook
compiler.hooks.compile.tap("CopyWebpackPlugin",(compilation)=>{
console.log("$$$")
})
debugger
// 异步hook
compiler.hooks.emit.tapAsync("CopyWebpackPlugin",(compilation ,cb)=>{ //compilation 存放本次编译相关的内容
console.log(compilation.assets,777)
compilation.assets[this.options.filename] ={
source:()=>{
return fs.readFileSync(path.resolve(__dirname,"../"+this.options.template))
}
}
cb()
})
}
}
module.exports = CopyWebpackPlugin;
const CopyWebpackPlugin = require("./plugins/copy-webpack-plugin");
module.exports = {
entry: "./index.js",
output: {
clean: true,
},
plugins: [new CopyWebpackPlugin({ template: "./src/index.html",filename:"fbb.html" })],
module: {},
};
编写一个bundle
目的:将两个模块打包成一个可以在浏览器中打开的bundle.js
// index.js
var add = require('add.js').default
console.log(add(1 , 2))
// add.js
exports.default = function(a,b) {return a + b}
分析:浏览器中没有exports对象与require方法
1.模拟require对象
exports = {}
eval('exports.default = function(a,b) {return a + b}') // node文件读取后的代码字符串
exports.default(1,3)
模块之间应该项目独立 所以将代码放在一个字之行函数中
var exports = {}
(function (exports, code) {
eval(code)
})(exports, 'exports.default = function(a,b){return a + b}')
2 模拟require方法
function require(file) {
var exports = {};
(function (exports, code) {
eval(code)
})(exports, 'exports.default = function(a,b){return a + b}')
return exports
}
var add = require('add.js').default
console.log(add(1 , 2))
将所有模块的文件名和代码字符串整理为一张key-value表就可以根据传入的文件名加载不同的模块了
(function (list) {
function require(file) {
var exports = {};
(function (exports, code) {
eval(code);
})(exports, list[file]);
return exports;
}
require("index.js");
})({
"index.js": `
var add = require('add.js').default
console.log(add(1 , 2))
`,
"add.js": `exports.default = function(a,b){return a + b}`,
});
webpack生成的bundle.js文件中还需要增加模块间的依赖关系
{
'./src/index.js': {
deps: { './add.js': './src/add.js', './minus.js': './src/minus.js' },
code: '"use strict";\n' +
'\n' +
'var _add = require("./add.js");\n' +
'\n' +
'var _minus = require("./minus.js");\n' +
'\n' +
'console.log((0, _add.add)(9, 8));\n' +
'console.log((0, _minus.minus)(6, 20));'
},
'./src/add.js': {
deps: { './minus.js': './src/minus.js' },
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports.add = void 0;\n' +
'\n' +
'var _minus = require("./minus.js");\n' +
'\n' +
'var add = function add(a, b) {\n' +
' return a + b;\n' +
'};\n' +
'\n' +
'exports.add = add;\n' +
'console.log((0, _minus.minus)(8, 5));'
},
'./src/minus.js': {
deps: {},
code: '"use strict";\n'
}
}
考虑到使用ES6 还应将代码转换成ES5
所以步骤如下:1.分析以来关系,2 代码转化 3替换exports 和require
cnpm install @babel/parser
cnpm install @babel/traverse
cnpm install @babel/core
cnpm install @babel/preset-env
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const babel = require("@babel/core");
function getModuleInfo(file) {
// 读取文件
const body = fs.readFileSync(file, "utf-8");
// 转化AST语法树
const ast = parser.parse(body, {
sourceType: "module", //表示我们要解析的是ES模块
});
// 依赖收集
const deps = {};
traverse(ast, {
ImportDeclaration({ node }) {
const dirname = path.dirname(file);
const abspath = "./" + path.join(dirname, node.source.value);
deps[node.source.value] = abspath;
},
});
// ES6转成ES5
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"],
});
const moduleInfo = { file, deps, code };
return moduleInfo;
}
const info = getModuleInfo("./src/index.js");
console.log("info:", info);
抽象语法树:
info:
{
file: './src/index.js',
deps: { './add.js': './src/add.js', './minus.js': './src/minus.js' },
code: '"use strict";\n' +
'\n' +
'var _add = require("./add.js");\n' +
'\n' +
'var _minus = require("./minus.js");\n' +
'\n' +
'console.log((0, _add.add)(9, 8));\n' +
'console.log((0, _minus.minus)(6, 20));'
}
找到依赖图谱
function parseModules(file) {
const entry = getModuleInfo(file);
const temp = [entry];
const depsGraph = {};
for(let i = 0;i<temp.length;i++){
const item = temp[i]
const {deps} = item;
if(deps){
for(let j in deps){
temp.push(getModuleInfo(deps[j]))
}
}
}
console.log(temp,222)
temp.forEach((moduleInfo) => {
depsGraph[moduleInfo.file] = {
deps: moduleInfo.deps,
code: moduleInfo.code,
};
});
console.log(depsGraph)
return depsGraph;
}
3.执行函数写文件
function bundle(file) {
const depsGraph = JSON.stringify(parseModules(file));
return `(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
var exports = {};
(function (require,exports,code) {
eval(code)
})(absRequire,exports,graph[file].code)
return exports
}
require('${file}')
})(${depsGraph})`;
}
const bundleJs = bundle("./src/index.js");
console.log(bundleJs,"bundleJs")
!fs.existsSync("./dist") && fs.mkdirSync("./dist");
fs.writeFileSync("./dist/bundle.js", bundleJs);