webpack 分享

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 : lengthmd5

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"]//注意顺序
    }
名称默认值描述
modulesfalse启用/禁用 CSS 模块
importLoaders0使用@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相关的配置

配置参照babel文档

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 选项 选项安装命令
falsenpm install --save @babel/runtime
2npm install --save @babel/runtime-corejs2
3npm 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

详细文档: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: "孙悟空",
          },
        },
      },
    ],
  },
  1. 自己编写的loader要使用path.resolve(__dirname, “./path”) 如果想直接用名称 需要配置resolveLoader
  2. this.query 可以获取options里面传递的参数
  3. this.callback 可以传递多个参数出去
  4. 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);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值