实现webpack

155 阅读13分钟

前言

如果你还不会webpack,就来一起学习一下吧,本文主要是依据webpack5 来进行学习

基础篇

mode

告诉webpack使用相应模式进行内置优化

  • development
    • 自动会设置 devtool: 'eval'
  • production (默认)

devtool

控制是否生成source map 以及如何生成source map

  • eval (默认)
  • false
  • source-map
  • eval-source-map base64的方式放在eval 后面
  • Inline-source-map 在最下面有一个sourceMappingUrl = base64
  • cheap-source-map。只有行信息,没有列信息
  • cheap-module-source-map(推荐) 对loader 处理的文件展示更加友好
  • hidden-source-map // 有map 文件生成,但是定位不到源代码,需要手动加载到环境下
  • nosource-source-map // 会生成map 文件,但是没有源文件提示

设置规则

(inline | hidden | eval )(nosources)(cheap|cheap-module )sourcemap

source Map

可以根据转换后的代码再次转换为源代码,方便定位调试

当将devtool 设置为source-map 的时候,在打包后,就会额外生成一个main.js.map 文件,格式化后,我们发现里面大致有这几个字段

version: 3   // 版本
sources: []  // 告诉我们将来的map文件是通过哪个源文件转换来的
names: [] // 对names里面的字符进行特殊的处理
mappings: 'xxx' // 类似于映射算法
file: 'x x x' // 源文件名
sourcesContent: [] // 源文件备份
sourceRoot: '' // 记录sourceMap文件的根路径

entry

打包的目录起点

entry: "./src/index.js",

output

  • path : 打包资源输出到哪一个目录

  • filename: 打包输出的文件名

  • publicPath: "", index.html内部引用打包后的js路径,默认是"",

​ 拼接的规则就是拿到前面的域名 + publicPath + filename

output: {
    path: path.resolve(__dirname, "dist"), // 打包的资源输出到哪个目录
    filename: "main[hash:6].js",
    publicPath: "",
    assetModuleFilename: "[name].[hash:6].[ext]", // 打包统一的目录下的名称
  },

resolve

配置当前模块解析规则

resolve: {
    // 配置模块解析规则
    extensions: [".js", ".jsx", ".tsx", ".ts", ".json"],
    alias: { // 配置别名
      "@": path.resolve(__dirname, "src"),
    },
  },

devServer

使用webpack-dev-server 的一系列配置

  • publicPath: 指定本地服务所在的目录,默认值为 / (项目所在目录)
  • contentBase: 打包之后的资源如果依赖了其他没有打包的资源,则告知去哪里找,绝对路径
  • watchContentBase: 和contenBase 配套,进行监听热更新
 devServer: {
    // webpack-dev-server 配置
    // 配置额外的静态文件目录
    contentBase: path.resolve(__dirname, "dist"),
    watchContentBase: true,
    compress: true, // 默认为false, 开启服务端gzip 的压缩
    port: 8080,
    open: true,
    hot: 'only';, // 热更新
    // hotOnly: true, // 后面文档更新为了hot 报错信息不会刷新页面
    publicPath: "/", // 后面文档更新为了static
    // historyApiFallback: true
    proxy: {}
  },

建议output 里面的publicPath 和devServer 里面的publicPath 设置为一样的

proxy

设置代理

 proxy: {
      // http:localhost:8000/api/users
      // https://api.github.com.api/api/users
      "/api": {
        targey: "https://api.github.com", // 实际代理的地址
        pathRewrite: {
          "^/api": "", // 将地址重写,将/api重写为“”, 想当于https://api.github.com.api/users
        },
        changeOrigin: true, // 修改host
      },
    },

loader

loader 是一个模块,将一些文件转换为webpack可以识别的模块,例如css模块不能进行webpack 进行打包,需要进行转换

css-loader 将css文件转换为一个对象,让webpack 能够识别css 语法,style-loader 将转换后的对象应用到页面上展示出style来,利用less-loader 来进行处理less文件(less 会把less 语法转为css)

  • css-loader

  • style-loader

  • less-loader

配置loader 语法

在webpack.config.js

module: {
	rules: [  // 添加多个不同文件的loader
		{
			test: /\.css$/,
			use: [ // 从下往上,从右边往左执行
        {
          loader: "style-loader"// options: ""  暂时不需要参数,所以不传
        },
        {
          loader: 'css-loader'
        }
			]
		},
	  { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] }, // 简写方式
	]
}
 module: {
    // loader 分类 pre前置 normal inline post
    rules: [
      { test: /\.txt$/, type: "asset/source" },
      //   { test: /\.css$/, use: ["style-loader", "css-loader"] },
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader",
            // options: "",
          },
          {
            loader: "css-loader",
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  // require("autoprefixer"),
                  require("postcss-preset-env"),
                ],
              },
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  // require("autoprefixer"),
                  require("postcss-preset-env"),
                ],
              },
            },
          },
          "less-loader",
        ],
      },
    ],
  },

我们发现在less 和css 中都会用到一样的loader,有一些冗余了,所以可以单独的拿出来一个文件进行配置

项目根目录下创建一个postcss.config.js

module.exports = {
  plugins: [require("postcss-preset-env")],
};

然后model 就可以直接写postcss-loader,就会自动找到他的配置文件postcss.config.js

 {
        test: /\.less$/,
        use: [
          "style-loader",
           {
            loader: "css-loader",
            options: {
              importLoaders: 1,  // 往前找一个,css-loader需要往前找一个loader执行,再次调用postcss-loader
            },
          },
          "postcss-loader",
          "less-loader",
        ]
 }

打包图片 file-loader

将图片也当作模块导入,webpack 默认不能处理,需要用到file-loader

// 引入图片
import oImagesrc from "./img/es.png";
import "./css/img.css";

function packImg() {
  // img 标签,src 属性
  const oEle = document.createElement("div");

  // 创建img标签,设置src
  const oImg = document.createElement("img");
  const requireEsModul = require("./img/es.png");
  console.log(requireEsModul, "---->requireEsModul");
  // file-loader 处理后,会将require 导入后的变为一个对象,里面的defalut属性才是图片地址
  // oImg.src = require("./img/es.png").default;
  
  // 如果file-loader 设置了options属性 esModule: false 则可以直接使用 
  // oImg.src = require("./img/es.png"); 
  
  // 利用import 将图片引入来进行使用
  oImg.src = oImagesrc;
  oEle.appendChild(oImg);

  // 设置背景图片
  const oBgImg = document.createElement("div");
  oBgImg.className = "bgBox";
  oEle.appendChild(oBgImg);

  return oEle;
}

document.body.appendChild(packImg());
  • 使用require 导入图片,如果不给file-loader 配置esmodule: false, 则需要.default 使用
  • 添加esmodule: false
  • 采用import xxx from 图片资源地址

webpack.config.js 配置

 {
        test: /\.(png|svg|gif|jpe?g)$/,
        use: [
          {
            loader: "file-loader",
            options: {
              esModule: false, // 是否转化后的包裹成esModule
              // 修改打包的图片的名称
              /**
               * 占位符号
               * [ext]: 扩展名称
               * [name]:文件名称
               * [hash]: 文件内容产出的hash
               * [contentHash]: hash值
               * [hash:<length>]  截取hash 长度
               * [path]: 路径
               */
              name: "[name].[hash:6].[ext]",
              outputPath: "img",
              // name: "img/[name].[hash:6].[ext]", 简写设置文件打包后的地址以及名称等
            },
          },
        ],
      },

打包图片的时候还有一个是url-loader,使用和file-loader差不多,但是

url-loader 会将图片以base64来进行打包到代码中,减少请求次数,而file-loader则会将图片拷贝到dist 目录下,分开请求,在url-loader 内部也可以调用file-loader. 利用limit 属性

 {
        test: /\.(png|svg|gif|jpe?g)$/,
        use: [
          {
            loader: "url-loader",
            options: {
              esModule: false, 
              name: "img/[name].[hash:6].[ext]",
              limit: 25 * 1024, // 超过就拷贝,没有超过就转base64
            },
          },
        ],
  }

webpack5不需要再去配置file-loader 或者url-loader,他新增了一个asset module type 模块

  • asset/resource == file-loader
  • asset/inline == url-loader
  • asset/source == raw-loader
  • asset. url-loader + limit
// 01 方式一 
{
        test: /\.(png|svg|gif|jpe?g)$/,
        type: "asset/resource",
        // 打包到统一的目录下面 这里也可以在output 下面配置。与这里的区别是,output配置是只要经过asset 都打包到统一的目录下面
        generator: {
          filename: "img/[name].[hash:6][ext]",
        },
  },
    
// 02 方式二
   {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: "asset",
        generator: {
          filename: "img/[name].[hash:6][ext]",
        },
        parser: {
          dataUrlCondition: {  // 相当于limit
            maxSize: 25 * 1024,
          },
        },
    }
  
 output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
    // 只要经过asset 都打包到统一的目录下面
    assetModuleFilename: "[name].[hash:6][ext]",
  },

asset 处理图标字体资源

自己实现一个loader

browerslistrc 配置

用来配置项目中兼容哪些平台 可以在caniuse.com/中查看(https:/…

安装webpack的时候,已经默认安装配置了,在nodeModules 中的 browserslist这个包,可以利用npx browserslist 来查看当前兼容的浏览器版本

两种配置方法:

  • package.json
"browserlist": [
	">1%",
	"last 2 version",
	"not dead"
]
  • 项目下新建 .browserslistrc
>1%
last 2 version
not dead

postcss

就是通过javascript 来转换样式的工具

比如说给css 补充前缀,做一些css的样式做一些兼容性的处理满足更多的浏览器

npm i postcss posts-cli

  • postcss。相当于一个转换样式的工具
  • postcss-cli. // 安装后,可以在命令行直接使用npx postcss
npx postcss -o ret.css ./src/test.css   // 将test.css 输出到ret.css 中

安装autoprefixer 插件 用来自动安装前缀( autoprefixer.github.io/ 添加前缀)

npx postcss --use autoprefixer -o ret.css ./src/test.css // 将test.css 输出到ret.css 中,添加前缀

如果很多样式都需要添加前缀,所以我们利用postcss-loader 这个loader,在配置文件中设置 npm i postcss-loader

module: {
	rules: [ 
	  { 
      test: /\.css$/, 
      use: [
        	"style-loader", 
        	"css-loader", 
	  	 		{
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [require("autoprefixer")], // 利用插件
              },
            },
          },
	  			] 
    }, 
	]
}
postcss-preset-env 预设(插件的集合)

集合了很多的常见的插件的集合

 {
   loader: "postcss-loader",
   options: {
  	 postcssOptions: {
     plugins: [
       // require("autoprefixer"),
       require("postcss-preset-env"),
     ],
   	},
 },

babel-loader配置

babel 简单了解

安装babel核心包
npm i @babel/core -D

安装脚手架执行babel
@babel/cli -D

安装工具包
@babel/plugin-transform-arrow-function
@babel/plugin-transform-block-scoping

执行使用babel
npx babel src/babeltest.js --out-dir build --plugins=@babel/plugin-transform-arrow-functions


  • npm i babel-loader 安装babel-loader
  • npm i @babel/preset-env 安装预设 或者安装自己需要的plugin
   {
        test: /\.js/,
        exclude: /node_modules/,  // node_modules中的不做处理
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env"], // 预设,就可以不用一个个写plugin
              // 也可以在后面直接配置指定浏览器、
              // presets: [["@babel/preset-env",{ targets: "chrome 91" }]],
              plugins: [],
            },
          },
        ],
      },

babel-loader 在webpack 打包的时候,是会根据.browserslistrc 文件来进行转换的

所以可以把babel-loader 配置文件单独拿出去

  • bable.config.js(json cis mjs)
  • babelrc.json(js)

项目根目录下新建babel.config.js 文件

module.exports = {
  presets: [["@babel/preset-env" /*{ targets: "chrome 91" }*/]],
};

然后webpack.config.js 就可以改为
{
	test: /\.js$/,
	use: ['babel-loader']
}

polyfill

Babel 的预设不一定可以将所有的语法都能转为浏览器兼容可以使用的,

所以当遇到最新的语法的时候,需要使用polyfill

Webpack5 之前需要安装 @babel/polyfill npm i @babel/polyfill --save, 但是打包会消耗更多时间,因为不能按需配置

所以webpack5只要 core-js. 和 regenerator-runtime,不需要@babel/polyfill

npm i core-js regenerator-runtime

然后给babel.config.js 中进行配置

useBuiltIns

  • usage
  • entry
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        // useBuiltIns: false, // 不对当前js处理做polyfill 填充
        useBuiltIns: "usage", // 使用这个后,会根据用户源代码当中所使用的新语法进行填充,corejs 会默认使用2版本,所以需要指定版本corejs 为3
        // useBuiltIns: "entry", // 依据所要兼容的浏览器browserslistrc来进行填充,不会去管源代码用没用,只要浏览器不兼容,就会把它全部填充进来,如果需要按需加载,需要在源代码中添加引入 core-js 和 regenerator-runtime
        // import "core-js/stable"
        // import "regenerator-runtime/runtime"
        corejs: 3,
      },
    ],
  ],
};

ts-loader

 {
   test: /\.ts$/,
   use: ["ts-loader"],
 }

转换ts 文件 ,将ts 文件语法转为javascript, 但是如果文件中用到新特性之类的,还是需要babel-loader 来进行添加polyfill转换,安装**@babel/preset-typescript**


 {
   test: /\.ts$/,
   use: ["babel-loader"],
 },


module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        // useBuiltIns: false, // 不对当前js处理做polyfill 填充
        useBuiltIns: "usage", // 使用这个后,会根据用户源代码当中所使用的新语法进行填充,corejs 会使用2版本,所以需要指定版本corejs 为3
        //  useBuiltIns: "entry", // 依据所要兼容的浏览器browserslistrc来进行填充,不会去管源代码用没用,只要浏览器不兼容,就会把它全部填充进来,如果
        // 需要按需加载,需要在源代码中添加
        corejs: 3,
      },
    ],
    ["@babel/preset-typescript"],
  ],
};

但是使用babel-loader 如果代码有语法错误,在编译阶段,是不会暴露出来的,只会在运行的时候发现,但是使用ts-loader 却可以

这个时候,我们即希望在编译的时候进行语法校验,也希望babel-loader进行polyfill 填充,所以利用pack.json 方式,在build的时候,同时利用tsc检查一个语法

 "tscck": "tsc --noEmit"   // 只会校验语法,但是不会产出新的js 文件
"scripts": {
    "start": "webpack serve",
    "build": "npm run tscck && webpack",
    "tscck": "tsc --noEmit"
  },

plugin

loader 和plugin 区别

loader 对特定的模块类型进行转换, 读取某一个资源类型的内容时候使用

plugin 可以做更多的事情,可以在webpack 打包的任意生命周期中做一些事情

clean-webpack-plugin

清空dist 目录

html-webpack-plugin

配置打包后的index.html

const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWbpackPlugin = require("html-webpack-plugin");

webpack.config.js

plugins: [
    new HtmlWbpackPlugin({ template: "./src/index.html" }),
    new CleanWebpackPlugin(),
 ],
definePlugin

webpack 自带的默认的插件,可以使用全局的常量

const { DefinePlugin } = require("webpack");

// 比如配置一个BASE_URL的常量
 plugins: [
    new DefinePlugin({
      BASE_URL: 'lalala', // 注意: 这里会把这个值直接赋值到常量中去
    }),
  ],

copy-webpack-plugin

有时候,public 中不希望webpack进行打包,所以需要进行拷贝

注意: 这里需要9版本,10版本会报错

const CopyWebpackPlugin = require("copy-webpack-plugin");

 new CopyWebpackPlugin({
      patterns: [
        { from: "public" }, // 不写to 的话,会自动复制到配置的output 这个目录
        // { from: "other", to: "public" },
      ],
webpack-dev-server

可以用webpack 打包的时候监听,用vscode 的live server 来进行查看,但是不能局部刷新,也可以在配置文件里面配置watch: true, 同样,有性能问题,

webpack-dev-server 可以实现局部刷新打包

npm i webpack-dev-server

 "start": "webpack serve",
 "build": "webpack"
 "wacth": "webpack --watch"
实现plugin

自己实现一个plugin

webpack.config.js

为了区分生产环境打包配置和开发环境打包配置,我们进行了对webppack.config.js 进行拆分配置。分为三个文件,一个基础文件配置,在生产和开发都会用到,然后一个生产环境配置,一个开发环境配置。通过利用 webpack-merge 将配置文件合并

首先需要修改我们的package.json 文件中的script, 将打包命令执行的webpack配置文件目录通过--config指定为我们想要执行的那个文件,然后通过 --env 来传入当前环境的参数

"scripts": {
    "build": "webpack --config ./config/webpack.common.js --env production",
    "serve": "webpack serve --config ./config/webpack.common.js --env development",
  },

在根目录下新建config 文件夹,里面创建四个文件

  • webpack.common.js
  • webpack.dev.js
  • webpack.pro.js
  • pathUtils.js // 来配置绑定当前上下文路径

因为我们在写配置别名和output的时候,需要传入一个绝对路径,但是目前我们修改完后,webpack的配置文件路径也就对应不上了,所以利用pathUtils.js 里面的方法来保存一个绝对路径地址。

// pathUtils.js

const path = require("path");
// 获取当前运行地址
const currentDir = process.cwd();
// 根据当前根目录生成绝对路径地址
const resolveCurrent = (relativePath) => {
  return path.resolve(currentDir, relativePath);
};
module.exports = resolveCurrent;

然后来配置我们的webpack.common.js 文件,我们把公共的配置都抽离到这个文件中,然后利用 webpack-merge 将配置文件合并

const { merge } = require("webpack-merge");
// 导入确定路径的utils函数
const resolveCurrent = require("./pathalis");
// 导入公共插件
const HtmlWbpackPlugin = require("html-webpack-plugin");



// 导入其他配置文件
// 导入生产环境webpack配置
const prodConfig = require("./webpack.prod");
// 导入开发环境webpack配置
const devConfig = require("./webpack.dev");


// 导出webpack配置文件
// 这里导出一个函数是为了接受package.json里面传入的环境参数
module.exports = (env) => {
  // 根据传入环境变量判断
  const isProduction = env.production;
  // 根据当前打包模式判断要和哪一个配置合并
  const config = isProduction ? prodConfig : devConfig;
	// 利用webpack-merge合并配置文件并返回
  const mergeConfig = merge(baseConfig, config);
  return mergeConfig;
};


// // 定义对象,保存基础配置信息
const baseConfig = {
  entry: "./src/index.js",
  resolve: {
    // 配置模块解析规则
    extensions: [".js", ".jsx", ".tsx", ".ts", ".json"],
    alias: {
      "@": resolveCurrent("./src"),  // 修改路径
    },
  },
  output: {
    path: resolveCurrent("./dist"), // 打包的资源输出到哪个目录
    filename: "main[hash:6].js",
    publicPath: "",
  },

  module: {
    // loader 分类 pre前置 normal inline post
    rules: [
      //   { test: /\.css$/, use: ["style-loader", "css-loader"] },
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader",
            // options: "",
          },
          {
            loader: "css-loader",
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: "postcss-loader",
            // options: {
            //   postcssOptions: {
            //     plugins: [
            //       //   require("autoprefixer"),
            //       require("postcss-preset-env"),
            //     ],
            //   },
            // },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          "postcss-loader",
          //   {
          //     loader: "postcss-loader",
          //     options: {
          //       postcssOptions: {
          //         plugins: [
          //           //   require("autoprefixer"),
          //           require("postcss-preset-env"),
          //         ],
          //       },
          //     },
          //   },
          "less-loader",
        ],
      },
      // {
      //   test: /\.(png|svg|gif|jpe?g)$/,
      //   use: [
      //     {
      //       loader: "url-loader",
      //       options: {
      //         esModule: false, // 是否转化后的包裹成esModule
      //         // 修改打包的图片的名称
      //         // 占位符号

      //         //   [ext]: 扩展名称
      //         //   [name]:文件名称
      //         //  [hash]: 文件内容产出的hash
      //         //  [contentHash]: hash值
      //         //  [hash:<length>]  截取hash 长度
      //         //   [path]: 路径

      //         name: "[name].[hash:6].[ext]",
      //         // name: "img/[name].[hash:6].[ext]",
      //         outputPath: "img",
      //         limit: 25 * 1024, // 超过就拷贝,没有超过就转base64
      //       },
      //     },
      //   ],
      // },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: "asset",
        // 只需要经过asset 都打包到统一的目录下面
        generator: {
          filename: "img/[name].[hash:6][ext]",
        },
        parser: {
          dataUrlCondition: {
            maxSize: 25 * 1024,
          },
        },
      },
      {
        test: /\.js/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
            // options: {
            //   presets: [["@babel/preset-env" /*{ targets: "chrome 91" }*/]],
            //   plugins: [],
            // },
          },
        ],
      },
      {
        test: /\.ts$/,
        use: ["babel-loader"],
      },
    ],
  },

  plugins: [
    new HtmlWbpackPlugin({
      template: "./src/index.html",
      title: "lalala",
    }),
  ],
};

公共的配置完成后,只需要写入不同环境下的配置就可以了

在webpack.prod.js 中写入生产环境配置

const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  mode: "production",
  devtool: "source-map",
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        { from: "public" },
        // { from: "other", to: "public" },
      ],
    }),
  ],
};

同理,在webpack.dev.js中写入开发环境配置

console.log(1111);

const path = require("path");
const HtmlWbpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const { DefinePlugin } = require("webpack");

module.exports = (env) => {
  console.log(env, "---->dfadsfs");
  const isProduction = env.production;
  return {
    // watch: true, // 默认为false
    mode: "development",
    entry: "./src/index.js",
    resolve: {
      // 配置模块解析规则
      extensions: [".js", ".jsx", ".tsx", ".ts", ".json"],
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
    devtool: "source-map",
    output: {
      path: path.resolve(__dirname, "dist"), // 打包的资源输出到哪个目录
      filename: "main[hash:6].js",
      publicPath: "",
      assetModuleFilename: "[name].[hash:6].[ext]", // 打包统一的目录下的名称
    },
    target: "web",
    devServer: {
      // webpack-dev-server 配置
      // 配置额外的静态文件目录
      contentBase: path.resolve(__dirname, "dist"),
      watchContentBase: true,
      compress: true, // 默认为false, 开启服务端gzip 的压缩
      port: 8080,
      open: true,
      hot: true, // 热更新
      hotOnly: true, // 报错信息不会刷新页面
      publicPath: "/",
      // historyApiFallback: true
      proxy: {
        // http:localhost:8000/api/users
        // https://api.github.com.api/api/users
        "/api": {
          targey: "https://api.github.com", // 实际代理的地址
          pathRewrite: {
            "^/api": "", // 将地址重写,将/api重写为“”, 想当于https://api.github.com.api/users
          },
          changeOrigin: true, // 修改host
        },
      },
    },
    /**
     * loader 是什么,为啥用loader
     * loader 是一个模块,将一些文件转换为webpack可以识别的模块,例如css 需要进行转换
     */
    module: {
      // loader 分类 pre前置 normal inline post
      rules: [
        { test: /\.txt$/, type: "asset/source" },
        { test: /\.txt$/, type: "asset/source" },
        //   { test: /\.css$/, use: ["style-loader", "css-loader"] },
        {
          test: /\.css$/,
          use: [
            {
              loader: "style-loader",
              // options: "",
            },
            {
              loader: "css-loader",
              options: {
                importLoaders: 1,
              },
            },
            {
              loader: "postcss-loader",
              // options: {
              //   postcssOptions: {
              //     plugins: [
              //       //   require("autoprefixer"),
              //       require("postcss-preset-env"),
              //     ],
              //   },
              // },
            },
          ],
        },
        {
          test: /\.less$/,
          use: [
            "style-loader",
            "css-loader",
            "postcss-loader",
            //   {
            //     loader: "postcss-loader",
            //     options: {
            //       postcssOptions: {
            //         plugins: [
            //           //   require("autoprefixer"),
            //           require("postcss-preset-env"),
            //         ],
            //       },
            //     },
            //   },
            "less-loader",
          ],
        },
        // {
        //   test: /\.(png|svg|gif|jpe?g)$/,
        //   use: [
        //     {
        //       loader: "url-loader",
        //       options: {
        //         esModule: false, // 是否转化后的包裹成esModule
        //         // 修改打包的图片的名称
        //         // 占位符号

        //         //   [ext]: 扩展名称
        //         //   [name]:文件名称
        //         //  [hash]: 文件内容产出的hash
        //         //  [contentHash]: hash值
        //         //  [hash:<length>]  截取hash 长度
        //         //   [path]: 路径

        //         name: "[name].[hash:6].[ext]",
        //         // name: "img/[name].[hash:6].[ext]",
        //         outputPath: "img",
        //         limit: 25 * 1024, // 超过就拷贝,没有超过就转base64
        //       },
        //     },
        //   ],
        // },
        {
          test: /\.(png|svg|gif|jpe?g)$/,
          type: "asset",
          // 只需要经过asset 都打包到统一的目录下面
          generator: {
            filename: "img/[name].[hash:6][ext]",
          },
          parser: {
            dataUrlCondition: {
              maxSize: 25 * 1024,
            },
          },
        },
        {
          test: /\.js/,
          exclude: /node_modules/,
          use: [
            {
              loader: "babel-loader",
              // options: {
              //   presets: [["@babel/preset-env" /*{ targets: "chrome 91" }*/]],
              //   plugins: [],
              // },
            },
          ],
        },
        {
          test: /\.ts$/,
          use: ["babel-loader"],
        },
      ],
    },

    plugins: [
      new HtmlWbpackPlugin({
        template: "./src/index.html",
        title: "lalala",
      }),
      new CleanWebpackPlugin(),
      // new DefinePlugin({
      //   BASE_URL: '"lalala"',
      // }),
      // new CopyWebpackPlugin({
      //   patterns: [
      //     { from: "public" },
      //     // { from: "other", to: "public" },
      //   ],
      // }),
    ],
  };
};

用到的依赖版本,有的依赖在实际项目中其实并不需要安装

"dependencies": {
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-typescript": "^7.16.7",
    "autoprefixer": "^10.4.2",
    "core-js": "^3.21.0",
    "css-loader": "^5.2.6",
    "html-webpack-plugin": "^5.3.1",
    "less": "^4.1.1",
    "less-loader": "^9.0.0",
    "postcss-loader": "^6.2.1",
    "regenerator-runtime": "^0.13.9",
    "style-loader": "^2.0.0",
    "ts-loader": "^9.2.6",
    "webpack": "^5.38.1",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^3.11.2"
  },
  "devDependencies": {
    "@babel/cli": "^7.17.0",
    "@babel/core": "^7.17.2",
    "@babel/plugin-transform-arrow-functions": "^7.16.7",
    "babel-loader": "^8.2.3",
    "clean-webpack-plugin": "^4.0.0",
    "copy-webpack-plugin": "^9.0.0",
    "file-loader": "^6.2.0",
    "postcss": "^8.4.6",
    "postcss-cli": "^9.1.0",
    "postcss-preset-env": "^7.3.1",
    "url-loader": "^4.1.1"
  }

基础篇完结

原理篇

手写loader

Loader 本质上是一个函数, loader分类 pre前置. normal. inline post

我们先来看下loader的执行顺序, 一般我们是这么写loader

module: {
	rules: [
		{
			test: /\.js$/,
			use: [
				// loader1, loader2, loaser3
				{
					loader: 'loader1',
					options: {
						name: 'xxxx',
						age: 18
					}
				},
				{
					loader: 'bablelLoader',
					options: {
						presets: ["@babel/preset-env"]
					}
				}
			]
		}
	]
}

解析到loader 是一个数组的时候,会从左往右解析,每一个loader 其实都有一个pitch 方法,解析到第一个loader的时候,会执行第一个loader的pitch 方法, 然后解析到第2个loader的时候,秽执行第二个loader的pitch 方法,解析完成后,loader 会开始从下往上,从右往左开始执行

// module.exports.pitch = () => {
//   console.log(111);
// };

loader 有同步loader,也有异步loader, 可以同步执行,也可以异步执行

同步loader

// 写法一
module.exports = (content, map, meta) => {
	console.log(content)
	return content
}
// 写法二
module.exports = function(content, map, meta) {
	this.callback(null, content, map, meta)
}

异步loader写法

module.exports = function(content, map, meta) {
  const callback = this.async() // 在这里阻塞下一个loader执行,但不会阻塞其他操作,当调用callback 的时候,loader 才会往下执行
  setTimeout(() => {  
 		 callback(null, content, map, meta)
  }, 1000) // 一秒后loader 才会往下执行
}

我们来写一个简单的loader实现babel转换功能 ,里面用到loader-utils 来获取loader中的属性,用schema-utils 来验证loader传入参数的格式

const { getOptions } = require("loader-utils"); // 来获取loader传入的参数
const { validate } = require("schema-utils"); // 验证loader 传入参数的格式
const schema = require("./schema.json");

const babel = require("@babel/core");
const util = require("util");


module.exports = function(content, map, meta) {
  	const options = getOptions(this); // 拿到传给loader的参数 // this.getOptions()也可以获取
  
  // 校验loader 参数
  validate(schema, options, {
    name: 'loader options 传入参数格式错误'  // 校验失败提示信息
  })
  
  const callback = this.async(); // 会在这里阻塞,等到调用callback的时候,loader才会往下执行
  
  // 使用bable 做编译
  transform(content, options).then(({code, map}) => {
		return callback(null, code, map, meta)
  }).catch((e) => callback(e) )
  	
  setTimeout(() => {
    callback(null, content, map, meta)
  }, 1000) // 一秒后loader往下执行

}

schema.json

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "描述, additionalProperties 表示能不能追加属性"
    }
  },
  "additionalProperties": true
}

这样,我们就可以使用自己的loader了,不过要在使用的时候,将其导入进来

{
	 test: /\.js$/,
        loader: path.resolve(__dirname, "loaders", "loader1"), // loaders 是放loader的文件夹,loader1是loader文件名
        use: [
           {
             loader: "loader1",
             options: {
               name: "lalala",
               age: 18,
             },
           },
        ]
}

手写plugin

plugin 是一个类,他利用tapable,里面提供了webpack生命周期不同时期的钩子函数(有同步钩子,异步钩子等),可以让plugin注册对应不同钩子的事件处理函数,然后在webpack 对应的生命周期的时候触发执行这些事件处理函数。

我们简单使用一个这个tapable 库

// npm i tapable -D
const {
  SyncHook,
  SyncBailHook,
  AsyncParallelHook,
  AsyncSeriesHook,
} = require("tapable");

// compiler 钩子,相当于生命周期函数,在触发compiler 的时候,会创建compildation
// compilation 钩子 ,其实是一个对象
class Lesson {
  constructor() {
    // 初始化hooks容器
    this.hooks = {
      // t同步勾子
      go: new SyncHook(["address"]),
      // go: new SyncBailHook(["address"]), // 一旦遇到return 就不会再执行,直接退出
      // 异步并行勾子,里面并行执行
      leave: new AsyncParallelHook(["name", "age"]),
      // 异步串行 AsyncSeriesHook
      // leave: new AsyncSeriesHook(["name", "age"]),
    };
  }
  tap() {
    // 往hooks容器中注册事件/添加回调函数

    // 注册同步任务
    this.hooks.go.tap("class0318", (address) => {
      console.log("class0318", address);
    });

    this.hooks.go.tap("class0410", (address) => {
      console.log("class0410", address);
    });

    // 注册异步任务
    this.hooks.leave.tapAsync("class0510", (name, age, cb) => {
      setTimeout(() => {
        console.log("class0510", name, age);
        cb();
      }, 1000);
    });

    this.hooks.leave.tapPromise("class0610", (name, age) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log("class0510", name, age);
          resolve();
        }, 1000);
      });
    });
  }
  start() {
    // 触发hooks
    this.hooks.go.call("c318");

    // 触发异步勾子

    this.hooks.leave.callAsync("lalla", 18, () => {
      // 所有leavae 容器中的函数触发完了,才触发
      console.log("end");
    });
  }
}

const l = new Lesson();
l.tap();
l.start(); // classo318 c318
// class0410 c318

Plugin1.js

class Plugin1 {
  apply(complier) {
    complier.hooks.emit.tap("plugin1", (compilation) => {
      console.log("emit.tap");
    });

    // 异步串行勾子
    complier.hooks.emit.tapAsync("plugin1", (compilation, cb) => {
      setTimeout(() => {
        console.log("emit.tapAsync");
        cb(); // 一定要调用cb 才会执行
      }, 1000);
    });

    complier.hooks.emit.tapPromise("plugin1", (compilation, cb) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          console.log("emit.tapPromise");
          resolve(); // 一定要调用cb 才会执行
        }, 1000);
      });
    });

    complier.hooks.afterEmit.tap("plugin1", (compilation) => {
      console.log("afterEmit.tap");
    });

    complier.hooks.done.tap("plugin1", (compilation) => {
      console.log("done.tap");
    });
  }
}

module.exports = Plugin1;

Plugin2.js

// 利用node 开启调试模式
/**
 * package.json 中开启 --inspect-brk 相当于首行开启一个断点
 * “debugeStart”: "node --inspect-brk ./node_modules/webpack/bin/webpack.js"
 * 运行后,在要调试的地方打一个debugger
 */

const fs = require("fs");
const util = require("util");
const path = require("path");

// 快速创建一个基于webpack创建的文件类型
const webpack = require("webpack");
// RawSource 可以将读取文件的数据变成一个对象
const { RawSource } = webpack.sources;

// 将fs readFile 方法变成给予
// const readFile = util.promisify(fs.readFile);

class Plugin2 {
  apply(compiler) {
    // 在 thisCompilation 的时候,会初始化compilation
    compiler.hooks.thisCompilation.tap("Plugin2", (compilation) => {
      // debugger;
      // console.log(compilation, "0000");
      // 添加资源
      compilation.hooks.additionalAssets.tapAsync("Plugin2", (cb) => {
        // debugger;
        // console.log(compilation, "0000");

        const content = "hello word";
        // 往输出的资源中,添加一个a.txt 文件
        compilation.assets["a.txt"] = {
          // 文件大小
          size() {
            return content.length;
          },
          // 文件内容
          source() {
            return content;
          },
        };

        const data = fs.readFileSync(path.resolve(__dirname, "b.txt"));
        console.log(data, "--->data");
        compilation.assets["b.txt"] = new RawSource(data);

        // 另一种输出资源的方式,相当于 compilation.assets["b.txt"] = new RawSource(data);
        compilation.emitAsset("b.txt", new RawSource(data));

        cb();
      });
    });
  }
}

module.exports = Plugin2;

自己实现一个copyWebpackPlugin2

Schema.json

{
  "type": "object",
  "properties": {
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "ignore": {
      "type": "array"
    }
  },
  "additionalProperties": false
}

copyWebpackPlugin2.js

const { promisify } = require("util");
const path = require("path");
const { validate } = require("schema-utils");
const fs = require("fs");
// 专门用来匹配文件列表
const globby = require("globby");

const readFiless = promisify(fs.readFile);
const schema = require("./schema.json");

// 快速创建一个基于webpack创建的文件类型
const webpack = require("webpack");
// RawSource 可以将读取文件的数据变成一个对象
const { RawSource } = webpack.sources;

// 将fs readFile 方法变成给予
// const readFile = util.promisify(fs.readFile);

class CopyWebpackPlugin2 {
  constructor(options = {}) {
    // 验证options 是否合格
    validate(schema, options, {
      name: "CopyWebpackPlugin2",
    });

    this.options = options;
  }
  apply(compiler) {
    // 初始化compilation
    compiler.hooks.thisCompilation.tap("CopyWebpackPlugin2", (compilation) => {
      // 添加资源的hooks
      compilation.hooks.additionalAssets.tapAsync(
        "CopyWebpackPlugin2",
        async (cb) => {
          // 将from 中的资源复制到to 然后输出
          // 读取from 资源,过滤掉ignore 生成webpack资源,然后输出
          const { from, ignore } = this.options;
          const to = this.options.to ? this.options.to : ".";

          // 上下文地址 context 就是webpack配置 默认值是process.cwd
          const context = compiler.options.context;
          // 判断是不是绝对路径
          const absoluteFrom = path.isAbsolute(from)
            ? from
            : path.resolve(context, from);

          // globby(要处理的文件夹, options)
          const paths = await globby(absoluteFrom);
          console.log(paths, "--->paths");
          console.log(fs.readdirSync(paths[0]), "--->fs");
          const pathss = fs.readdirSync(paths[0]);

          // 读取paths 中所有资源
          const files = await Promise.all(
            pathss.map(async (abpath, index) => {
              const relative = `${paths[0]}/${abpath}`;
              console.log(relative, "--->relative");
              const data = await fs.readFileSync(relative);
              // const filename = path.basename(abpath);
              // 和to 属性结合 没有to xx.js  有to to/xx.js
              const filename = path.join(to, abpath);

              return { data, filename };
            })
          );

          // // 生成webpack 格式
          const assets = files.map((file) => {
            const source = new RawSource(file.data);
            return {
              source,
              filename: file.filename,
            };
          });

          assets.forEach((item) => {
            compilation.emitAsset(item.filename, item.source);
          });

          cb();
        }
      );
    });
  }
}

module.exports = CopyWebpackPlugin2;

然后在配置文件中引入使用

const Plugin1 = require("./plugins/Plugin1");
const Plugin2 = require("./plugins/Plugin2");
const CopyWebpackPlugin2 = require("./plugins/CopyWebpackPlugin2");  
plugins: [
    new HtmlWbpackPlugin({
      template: "./src/index.html",
      title: "lalala",
    }),
    // new Plugin1(),
    new Plugin2(),
    new CopyWebpackPlugin2({
      from: "public",
      to: "css",
      ignore: ["**/index.html"],
    }),
  ],

手写webPack

  • webpack执行流程

    引入webpack 配置文件,通过configFactory 来传入一个当前环境是dev还是pro,然后得到相应参数的webpack配置,最后会调用build方法,在bulid 方法内部会调用webpack ,并且传入一个webpack配置对象。得到一个compiler 对象,然后调用他的run 方法。执行

    用webpack 初始化一个compiler对象。然后去运行编译,在webpack.config.js 的entry 中开始打包

    打包的时候就会对文件进行处理利用loader 进行递归处理,直到所有的依赖都处理好,生成一副依赖图,编译模版,生成一个打包出来的chunk,然后再把每一个chunk根据依赖关系整合到输出列表中,然后通过fs将文件列表输出

首先我们在src下面新建一个lib文件夹,里面放置我们的webpack文件

新建一个index.js

const fs = require("fs");

const Compiler = require("./Compiler");

function mywebpack(config) {
  return new Compiler(config);
}

module.exports = mywebpack;

然后新建Compiler.js。[这里查看完整代码](#### Compiler代码(TODO: 未写完)) // TODO: 这里代码是不完整的

const fs = require("fs");
const path = require("path");
// 引入paser 解析为抽象语法树
const babelParser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");
const { getAst, getCode, getDepes } = require("./parser");

class Compiler {
  constructor(options = {}) {
    this.options = options;
  }

  // 启动webpack 打包
  run() {
    // 1. 读取入口文件内容
    const filePath = this.options.entry;
    // const file = fs.readFileSync(filePath, "utf-8");
    // // 第一步: 2. 将其解析成ast 抽象语法树
    // const ast = babelParser.parse(file, {
    //   sourceType: "module", // 解析文件的模块化方案是 ES Module
    // });

    // // 获取到文件文件夹路径
    // const dirname = path.dirname(filePath);

    // // debugger;
    // console.log(ast, "-->ast");
    // // 收集依赖,可以利用ast 中的progress 中的body 属性type: "ImportDeclaration"来判断
    // // 是不是属于文件引入,如果是,则找到他的source 中的value 拿到文件路径,这样比较麻烦
    // // 所以利用babel 中的traverse 来快速搜集依赖 @babel/traverse

    // // 定义存储依赖的容器
    // const deps = {};

    // // 第二步: 收集依赖
    // traverse(ast, {
    //   // 内部会遍历ast 中program.body, 判断里面语句类型
    //   // 如果type: ImportDeclaration 就会触发这个函数,并且参数为当前触发的那个语句
    //   ImportDeclaration(code) {
    //     // debugger;
    //     const { node } = code;
    //     // 文件的相对路径 '.add.js'
    //     const relativePath = node.source.value;
    //     // 根据入口文件生成基于入口文件的绝对路径
    //     const absolutePath = path.resolve(dirname, relativePath);
    //     // 添加依赖
    //     deps[relativePath] = absolutePath;
    //   },
    // });

    // console.log(deps);

    // // 第三步 编译代码: 将代码中浏览器中不能识别的代码进行编译 @babel/core
    // const { code } = transformFromAst(ast, null, {
    //   presets: ["@babel/preset-env"],
    // });

    // console.log(code, "--->code");

    // 1. 将文件解析为ast
    const ast = getAst(filePath);
    // 1. 获取ast 的依赖
    const deps = getDepes(ast, filePath);
    // 获取代码
    const code = getCode(ast);

    console.log(ast, deps, code, "--->");
  }
}

module.exports = Compiler;

新建一个parser.js, 将代码解析文件抽取出来

const fs = require("fs");
const path = require("path");
// 引入paser 解析为抽象语法树
const babelParser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");

const parser = {
  // 将文件解析为ast
  getAst(filePath) {
    // 读取文件
    const file = fs.readFileSync(filePath, "utf-8");
    // 第一步: 2. 将其解析成ast 抽象语法树
    const ast = babelParser.parse(file, {
      sourceType: "module", // 解析文件的模块化方案是 ES Module
    });

    return ast;
  },

  // 获取依赖
  getDepes(ast, filePath) {
    // 获取到文件文件夹路径
    const dirname = path.dirname(filePath);

    // 定义存储依赖的容器
    const deps = {};

    // 第二步: 收集依赖
    traverse(ast, {
      // 内部会遍历ast 中program.body, 判断里面语句类型
      // 如果type: ImportDeclaration 就会触发这个函数,并且参数为当前触发的那个语句
      ImportDeclaration(code) {
        // debugger;
        const { node } = code;
        // 文件的相对路径 '.add.js'
        const relativePath = node.source.value;
        // 根据入口文件生成基于入口文件的绝对路径
        const absolutePath = path.resolve(dirname, relativePath);
        // 添加依赖
        deps[relativePath] = absolutePath;
      },
    });

    return deps;
  },

  // ast解析成code
  getCode(ast) {
    // 第三步 编译代码: 将代码中浏览器中不能识别的代码进行编译 @babel/core
    const { code } = transformFromAst(ast, null, {
      presets: ["@babel/preset-env"],
    });

    return code;
  },
};

module.exports = parser;

在新建一个script文件夹,里面写入build.js,这样使用我们自己写的webpack进行打包

const mywebpack = require("../lib/mywebpack");
const config = require("../config/webpack.config");

const compiler = mywebpack(config);

// 开始打包
compiler.run();

在package.json中添加build命令

{
  "name": "my_webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "directories": {
    "lib": "lib"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "node ./script/build.js",
    "debug": "node --inspect-brk ./script/build.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/parser": "^7.17.3"
  },
  "dependencies": {
    "@babel/traverse": "^7.17.3"
  }
}

Compiler代码(TODO: 未写完)

const fs = require("fs");
const path = require("path");
// 引入paser 解析为抽象语法树
const babelParser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");
const { getAst, getCode, getDepes } = require("./parser");

class Compiler {
  constructor(options = {}) {
    // webpackConfig 的配置
    this.options = options;
    // 所有依赖容器
    this.modules = [];
  }

  // 启动webpack 打包
  run() {
    // 1. 读取入口文件内容
    const filePath = this.options.entry;
    // const file = fs.readFileSync(filePath, "utf-8");
    // // 第一步: 2. 将其解析成ast 抽象语法树
    // const ast = babelParser.parse(file, {
    //   sourceType: "module", // 解析文件的模块化方案是 ES Module
    // });

    // // 获取到文件文件夹路径
    // const dirname = path.dirname(filePath);

    // // debugger;
    // console.log(ast, "-->ast");
    // // 收集依赖,可以利用ast 中的progress 中的body 属性type: "ImportDeclaration"来判断
    // // 是不是属于文件引入,如果是,则找到他的source 中的value 拿到文件路径,这样比较麻烦
    // // 所以利用babel 中的traverse 来快速搜集依赖 @babel/traverse

    // // 定义存储依赖的容器
    // const deps = {};

    // // 第二步: 收集依赖
    // traverse(ast, {
    //   // 内部会遍历ast 中program.body, 判断里面语句类型
    //   // 如果type: ImportDeclaration 就会触发这个函数,并且参数为当前触发的那个语句
    //   ImportDeclaration(code) {
    //     // debugger;
    //     const { node } = code;
    //     // 文件的相对路径 '.add.js'
    //     const relativePath = node.source.value;
    //     // 根据入口文件生成基于入口文件的绝对路径
    //     const absolutePath = path.resolve(dirname, relativePath);
    //     // 添加依赖
    //     deps[relativePath] = absolutePath;
    //   },
    // });

    // console.log(deps);

    // // 第三步 编译代码: 将代码中浏览器中不能识别的代码进行编译 @babel/core
    // const { code } = transformFromAst(ast, null, {
    //   presets: ["@babel/preset-env"],
    // });

    // console.log(code, "--->code");

    // 第一次构建,得到入口文件信息
    const fileInfo = this.build(filePath);

    this.modules.push(fileInfo);

    // 递归搜集依赖 遍历所有的依赖
    this.modules.forEach((fileInfo) => {
      // {'x.xx': 'xxx.xxx'}
      // 取出当前文件所有依赖进行遍历
      const deps = fileInfo.deps;
      for (const relativePath in deps) {
        // 得到当前依赖文件的绝对路径
        const absolutePath = deps[relativePath];
        // 对依赖文件进行处理
        const fileInfo = this.build(absolutePath);
        // 将后面的依赖push
        this.modules.push(fileInfo);
      }
    });

    // console.log(this.modules, "-->所有依赖");

    // 将依赖整理成更好的依赖关系图

    // const arr = {
    //   "index.js": {
    //     code: "xxx",
    //     deps: {
    //       "add.js": "xxx",
    //     },
    //   },

    //   "add.js": {
    //     code: "xxx",
    //     deps: {},
    //   },
    // };

    const depsGraph = this.modules.reduce((graph, module) => {
      return {
        ...graph,
        [module.filePath]: {
          code: module.code,
          deps: module.deps,
        },
      };
    }, {});

    // 根据依赖关系图生成打包资源
    this.generate(depsGraph);
  }

  // 开始构建
  build(filePath) {
    // 1. 将文件解析为ast
    const ast = getAst(filePath);
    // 1. 获取ast 的依赖
    const deps = getDepes(ast, filePath);
    // 获取代码
    const code = getCode(ast);

    return {
      // 当前模块文件路径
      filePath,
      // 当前文件所有依赖
      deps,
      // 当前文件解析后的代码
      code,
    };
  }

  // 构建输出资源方法
  generate(depsGraph) {}
}

module.exports = Compiler;