基于 webpack4

从 webpack 命令行说起

  1. 通过 npm scripts 运行 webpack
  • 开发环境 npm run dev
  • 生产环境 npm run build
  1. 通过 webpack 直接运行
  • webpack entry.js bundle.js

在命令行运行以上命令后,npm 会让命令行工具进入 node_modules\.bin目录查找是否存在 webpack.sh 或者 webpack.cmd 文件。
如果存在,就执行。不存在就抛出错误

实际入口文件是:node_modules\webpack\bin\webpack.js

  1. // webpack4 的 webpack.js
  2. process.exitCode = 0; // 1. 正常执行返回
  3. const runCommand = (command, args) => {}; // 2. 运行某个命令
  4. const isInstalled = packageName => {}; // 3. 判断某个包是否安装
  5. const CLIs = [..]; // 4. webpack 可用的 CLI: webpack-cli 或者 webpack-command
  6. const installedClis = CLIs.filter(cli => cli.installed); // 5. 判断两个 CLI 是否有安装
  7. // 6. 根据安装数量进行处理
  8. if(installedClis.length == 0) {}
  9. else if(installedClis.length == 1) {}
  10. else {}

webpack-cli 和 webpack-command 有一个就可以运行 webpack。 webpack5 用的是 webpack-cli,它的功能更加丰富。

webpack-cli

webpack-cli 作用

  • 引入 yargs,对命令行的进行定制
  • 分析命令行参数,对各个参数进行转换,组成编译配置项
  • 引用 webpack,根据配置项进行编译和构建

webpack-cli 对命令行的 args 的分析
image.png

webpack-cli 执行的结果 webpack-cli 对配置文件和命令行参数进行转换,最终生成配置选项参数 options 最后会更具配置参数实例化 webpack 对象,然后执行构建流程

webpack 的本质
webpack 可以将其理解是一种基于事件流的编程范例,一系列的插件运行。

Tapable

Tapable 是一个类似于 Node.js 的 EventEmitter 的库,主要是控制钩子函数的发布订阅,控制着 webpack 的插件系统。
Tapable 库保留了很多 Hook 类,为插件提供挂载的钩子。
image.png
image.png

Tapable 的使用

钩子的绑定和执行

Tapable 提供了同步 & 异步绑定钩子的方法,并且他们都有绑定事件和执行事件的对应方法。


绑定 执行
Async tapAsync / tapPromise / tap callAsync/ promise
Sync tap call
  1. const { SyncHook } = require('tapable');
  2. const hook = new SyncHook(['arg1', 'arg2', 'arg3']);
  3. hook.tap('hook1', (arg1, arg2, arg3) => {
  4. console.log(arg1, arg2, arg3);
  5. })
  6. hook.call(1, 2, 3)

示例

image.png

  1. const { SyncHook, AsyncSeriesHook } = require('tapable');
  2. class Car {
  3. constructor() {
  4. this.hooks = {
  5. accelerator: new SyncHook(['newspeed']),
  6. brake: new SyncHook(),
  7. calculateRoutes: new AsyncSeriesHook(['source', 'target', 'routesList'])
  8. };
  9. }
  10. }
  11. const myCar = new Car();
  12. // 绑定事件
  13. myCar.hooks.brake.tap('WarningLampPlugin', () => {
  14. console.log('WarningLampPlugin');
  15. })
  16. myCar.hooks.accelerator.tap('LoggerPlugin', (newspeed) => {
  17. console.log('Accelerator', newspeed);
  18. })
  19. myCar.hooks.calculateRoutes.tapPromise('calculateRoutes Promise', (source, target, routesList) => {
  20. console.log('source', source);
  21. return new Promise((resolve, reject) => {
  22. setTimeout(() => {
  23. console.log('tapPromise to', source, target, routesList);
  24. resolve();
  25. }, 1000)
  26. })
  27. })
  28. // 执行事件
  29. myCar.hooks.brake.call();
  30. myCar.hooks.accelerator.call(200);
  31. // 开始计时,记录异步事件
  32. console.time('cost');
  33. myCar.hooks.calculateRoutes.promise('Async', 'hook', 'demo')
  34. .then(() => console.timeEnd('cost'))
  35. .catch(err => console.log(err))
  36. // 结果
  37. // WarningLampPlugin
  38. // Accelerator 200
  39. // source Async
  40. // tapPromise to Async hook demo
  41. // cost: 1.001s

webpack 的 hooks

  1. class Compiler {
  2. constuctor(context) {
  3. this.hooks = Object.freeze({
  4. /** @type {SyncHook<[]>} */
  5. initialize: new SyncHook([]),
  6. /** @type {SyncBailHook<[Compilation], boolean>} */
  7. shouldEmit: new SyncBailHook(["compilation"]),
  8. /** @type {AsyncSeriesHook<[Stats]>} */
  9. done: new AsyncSeriesHook(["stats"]),
  10. /** @type {SyncHook<[Stats]>} */
  11. afterDone: new SyncHook(["stats"]),
  12. /** @type {AsyncSeriesHook<[]>} */
  13. additionalPass: new AsyncSeriesHook([]),
  14. /** @type {AsyncSeriesHook<[Compiler]>} */
  15. beforeRun: new AsyncSeriesHook(["compiler"]),
  16. /** @type {AsyncSeriesHook<[Compiler]>} */
  17. run: new AsyncSeriesHook(["compiler"]),
  18. /** @type {AsyncSeriesHook<[Compilation]>} */
  19. emit: new AsyncSeriesHook(["compilation"]),
  20. /** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
  21. assetEmitted: new AsyncSeriesHook(["file", "info"]),
  22. /** @type {AsyncSeriesHook<[Compilation]>} */
  23. afterEmit: new AsyncSeriesHook(["compilation"]),
  24. /** @type {SyncHook<[Compilation, CompilationParams]>} */
  25. thisCompilation: new SyncHook(["compilation", "params"]),
  26. /** @type {SyncHook<[Compilation, CompilationParams]>} */
  27. compilation: new SyncHook(["compilation", "params"]),
  28. /** @type {SyncHook<[NormalModuleFactory]>} */
  29. normalModuleFactory: new SyncHook(["normalModuleFactory"]),
  30. /** @type {SyncHook<[ContextModuleFactory]>} */
  31. contextModuleFactory: new SyncHook(["contextModuleFactory"]),
  32. /** @type {AsyncSeriesHook<[CompilationParams]>} */
  33. beforeCompile: new AsyncSeriesHook(["params"]),
  34. /** @type {SyncHook<[CompilationParams]>} */
  35. compile: new SyncHook(["params"]),
  36. /** @type {AsyncParallelHook<[Compilation]>} */
  37. make: new AsyncParallelHook(["compilation"]),
  38. /** @type {AsyncParallelHook<[Compilation]>} */
  39. finishMake: new AsyncSeriesHook(["compilation"]),
  40. /** @type {AsyncSeriesHook<[Compilation]>} */
  41. afterCompile: new AsyncSeriesHook(["compilation"]),
  42. /** @type {AsyncSeriesHook<[Compiler]>} */
  43. watchRun: new AsyncSeriesHook(["compiler"]),
  44. /** @type {SyncHook<[Error]>} */
  45. failed: new SyncHook(["error"]),
  46. /** @type {SyncHook<[string | null, number]>} */
  47. invalid: new SyncHook(["filename", "changeTime"]),
  48. /** @type {SyncHook<[]>} */
  49. watchClose: new SyncHook([]),
  50. /** @type {AsyncSeriesHook<[]>} */
  51. shutdown: new AsyncSeriesHook([]),
  52. /** @type {SyncBailHook<[string, string, any[]], true>} */
  53. infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
  54. // TODO the following hooks are weirdly located here
  55. // TODO move them for webpack 5
  56. /** @type {SyncHook<[]>} */
  57. environment: new SyncHook([]),
  58. /** @type {SyncHook<[]>} */
  59. afterEnvironment: new SyncHook([]),
  60. /** @type {SyncHook<[Compiler]>} */
  61. afterPlugins: new SyncHook(["compiler"]),
  62. /** @type {SyncHook<[Compiler]>} */
  63. afterResolvers: new SyncHook(["compiler"]),
  64. /** @type {SyncBailHook<[string, Entry], boolean>} */
  65. entryOption: new SyncBailHook(["context", "entry"])
  66. });
  67. }
  68. }
  1. class Compilation {
  2. constructor(compiler, params) {
  3. this.hooks = Object.freeze({
  4. /** @type {SyncHook<[Module]>} */
  5. buildModule: new SyncHook(["module"]),
  6. /** @type {SyncHook<[Module]>} */
  7. rebuildModule: new SyncHook(["module"]),
  8. /** @type {SyncHook<[Module, WebpackError]>} */
  9. failedModule: new SyncHook(["module", "error"]),
  10. /** @type {SyncHook<[Module]>} */
  11. succeedModule: new SyncHook(["module"]),
  12. /** @type {SyncHook<[Module]>} */
  13. stillValidModule: new SyncHook(["module"]),
  14. /** @type {SyncHook<[Dependency, EntryOptions]>} */
  15. addEntry: new SyncHook(["entry", "options"]),
  16. /** @type {SyncHook<[Dependency, EntryOptions, Error]>} */
  17. failedEntry: new SyncHook(["entry", "options", "error"]),
  18. /** @type {SyncHook<[Dependency, EntryOptions, Module]>} */
  19. succeedEntry: new SyncHook(["entry", "options", "module"]),
  20. /** @type {SyncWaterfallHook<[(string[] | ReferencedExport)[], Dependency, RuntimeSpec]>} */
  21. dependencyReferencedExports: new SyncWaterfallHook([
  22. "referencedExports",
  23. "dependency",
  24. "runtime"
  25. ]),
  26. /** @type {SyncHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */
  27. executeModule: new SyncHook(["options", "context"]),
  28. /** @type {AsyncParallelHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */
  29. prepareModuleExecution: new AsyncParallelHook(["options", "context"]),
  30. /** @type {AsyncSeriesHook<[Iterable<Module>]>} */
  31. finishModules: new AsyncSeriesHook(["modules"]),
  32. /** @type {AsyncSeriesHook<[Module]>} */
  33. finishRebuildingModule: new AsyncSeriesHook(["module"]),
  34. /** @type {SyncHook<[]>} */
  35. unseal: new SyncHook([]),
  36. /** @type {SyncHook<[]>} */
  37. seal: new SyncHook([]),
  38. /** @type {SyncHook<[]>} */
  39. beforeChunks: new SyncHook([]),
  40. /** @type {SyncHook<[Iterable<Chunk>]>} */
  41. afterChunks: new SyncHook(["chunks"]),
  42. /** @type {SyncBailHook<[Iterable<Module>]>} */
  43. optimizeDependencies: new SyncBailHook(["modules"]),
  44. /** @type {SyncHook<[Iterable<Module>]>} */
  45. afterOptimizeDependencies: new SyncHook(["modules"]),
  46. /** @type {SyncHook<[]>} */
  47. optimize: new SyncHook([]),
  48. /** @type {SyncBailHook<[Iterable<Module>]>} */
  49. optimizeModules: new SyncBailHook(["modules"]),
  50. /** @type {SyncHook<[Iterable<Module>]>} */
  51. afterOptimizeModules: new SyncHook(["modules"]),
  52. /** @type {SyncBailHook<[Iterable<Chunk>, ChunkGroup[]]>} */
  53. optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]),
  54. /** @type {SyncHook<[Iterable<Chunk>, ChunkGroup[]]>} */
  55. afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]),
  56. /** @type {AsyncSeriesHook<[Iterable<Chunk>, Iterable<Module>]>} */
  57. optimizeTree: new AsyncSeriesHook(["chunks", "modules"]),
  58. /** @type {SyncHook<[Iterable<Chunk>, Iterable<Module>]>} */
  59. afterOptimizeTree: new SyncHook(["chunks", "modules"]),
  60. /** @type {AsyncSeriesBailHook<[Iterable<Chunk>, Iterable<Module>]>} */
  61. optimizeChunkModules: new AsyncSeriesBailHook(["chunks", "modules"]),
  62. /** @type {SyncHook<[Iterable<Chunk>, Iterable<Module>]>} */
  63. afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]),
  64. /** @type {SyncBailHook<[], boolean>} */
  65. shouldRecord: new SyncBailHook([]),
  66. /** @type {SyncHook<[Chunk, Set<string>, RuntimeRequirementsContext]>} */
  67. additionalChunkRuntimeRequirements: new SyncHook([
  68. "chunk",
  69. "runtimeRequirements",
  70. "context"
  71. ]),
  72. /** @type {HookMap<SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext]>>} */
  73. runtimeRequirementInChunk: new HookMap(
  74. () => new SyncBailHook(["chunk", "runtimeRequirements", "context"])
  75. ),
  76. /** @type {SyncHook<[Module, Set<string>, RuntimeRequirementsContext]>} */
  77. additionalModuleRuntimeRequirements: new SyncHook([
  78. "module",
  79. "runtimeRequirements",
  80. "context"
  81. ]),
  82. /** @type {HookMap<SyncBailHook<[Module, Set<string>, RuntimeRequirementsContext]>>} */
  83. runtimeRequirementInModule: new HookMap(
  84. () => new SyncBailHook(["module", "runtimeRequirements", "context"])
  85. ),
  86. /** @type {SyncHook<[Chunk, Set<string>, RuntimeRequirementsContext]>} */
  87. additionalTreeRuntimeRequirements: new SyncHook([
  88. "chunk",
  89. "runtimeRequirements",
  90. "context"
  91. ]),
  92. /** @type {HookMap<SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext]>>} */
  93. runtimeRequirementInTree: new HookMap(
  94. () => new SyncBailHook(["chunk", "runtimeRequirements", "context"])
  95. ),
  96. /** @type {SyncHook<[RuntimeModule, Chunk]>} */
  97. runtimeModule: new SyncHook(["module", "chunk"]),
  98. /** @type {SyncHook<[Iterable<Module>, any]>} */
  99. reviveModules: new SyncHook(["modules", "records"]),
  100. /** @type {SyncHook<[Iterable<Module>]>} */
  101. beforeModuleIds: new SyncHook(["modules"]),
  102. /** @type {SyncHook<[Iterable<Module>]>} */
  103. moduleIds: new SyncHook(["modules"]),
  104. /** @type {SyncHook<[Iterable<Module>]>} */
  105. optimizeModuleIds: new SyncHook(["modules"]),
  106. /** @type {SyncHook<[Iterable<Module>]>} */
  107. afterOptimizeModuleIds: new SyncHook(["modules"]),
  108. /** @type {SyncHook<[Iterable<Chunk>, any]>} */
  109. reviveChunks: new SyncHook(["chunks", "records"]),
  110. /** @type {SyncHook<[Iterable<Chunk>]>} */
  111. beforeChunkIds: new SyncHook(["chunks"]),
  112. /** @type {SyncHook<[Iterable<Chunk>]>} */
  113. chunkIds: new SyncHook(["chunks"]),
  114. /** @type {SyncHook<[Iterable<Chunk>]>} */
  115. optimizeChunkIds: new SyncHook(["chunks"]),
  116. /** @type {SyncHook<[Iterable<Chunk>]>} */
  117. afterOptimizeChunkIds: new SyncHook(["chunks"]),
  118. /** @type {SyncHook<[Iterable<Module>, any]>} */
  119. recordModules: new SyncHook(["modules", "records"]),
  120. /** @type {SyncHook<[Iterable<Chunk>, any]>} */
  121. recordChunks: new SyncHook(["chunks", "records"]),
  122. /** @type {SyncHook<[Iterable<Module>]>} */
  123. optimizeCodeGeneration: new SyncHook(["modules"]),
  124. /** @type {SyncHook<[]>} */
  125. beforeModuleHash: new SyncHook([]),
  126. /** @type {SyncHook<[]>} */
  127. afterModuleHash: new SyncHook([]),
  128. /** @type {SyncHook<[]>} */
  129. beforeCodeGeneration: new SyncHook([]),
  130. /** @type {SyncHook<[]>} */
  131. afterCodeGeneration: new SyncHook([]),
  132. /** @type {SyncHook<[]>} */
  133. beforeRuntimeRequirements: new SyncHook([]),
  134. /** @type {SyncHook<[]>} */
  135. afterRuntimeRequirements: new SyncHook([]),
  136. /** @type {SyncHook<[]>} */
  137. beforeHash: new SyncHook([]),
  138. /** @type {SyncHook<[Chunk]>} */
  139. contentHash: new SyncHook(["chunk"]),
  140. /** @type {SyncHook<[]>} */
  141. afterHash: new SyncHook([]),
  142. /** @type {SyncHook<[any]>} */
  143. recordHash: new SyncHook(["records"]),
  144. /** @type {SyncHook<[Compilation, any]>} */
  145. record: new SyncHook(["compilation", "records"]),
  146. /** @type {SyncHook<[]>} */
  147. beforeModuleAssets: new SyncHook([]),
  148. /** @type {SyncBailHook<[], boolean>} */
  149. shouldGenerateChunkAssets: new SyncBailHook([]),
  150. /** @type {SyncHook<[]>} */
  151. beforeChunkAssets: new SyncHook([]),
  152. // TODO webpack 6 remove
  153. /** @deprecated */
  154. additionalChunkAssets: createProcessAssetsHook(
  155. "additionalChunkAssets",
  156. Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
  157. () => [this.chunks],
  158. "DEP_WEBPACK_COMPILATION_ADDITIONAL_CHUNK_ASSETS"
  159. ),
  160. // TODO webpack 6 deprecate
  161. /** @deprecated */
  162. additionalAssets: createProcessAssetsHook(
  163. "additionalAssets",
  164. Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
  165. () => []
  166. ),
  167. // TODO webpack 6 remove
  168. /** @deprecated */
  169. optimizeChunkAssets: createProcessAssetsHook(
  170. "optimizeChunkAssets",
  171. Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
  172. () => [this.chunks],
  173. "DEP_WEBPACK_COMPILATION_OPTIMIZE_CHUNK_ASSETS"
  174. ),
  175. // TODO webpack 6 remove
  176. /** @deprecated */
  177. afterOptimizeChunkAssets: createProcessAssetsHook(
  178. "afterOptimizeChunkAssets",
  179. Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE + 1,
  180. () => [this.chunks],
  181. "DEP_WEBPACK_COMPILATION_AFTER_OPTIMIZE_CHUNK_ASSETS"
  182. ),
  183. // TODO webpack 6 deprecate
  184. /** @deprecated */
  185. optimizeAssets: processAssetsHook,
  186. // TODO webpack 6 deprecate
  187. /** @deprecated */
  188. afterOptimizeAssets: afterProcessAssetsHook,
  189. processAssets: processAssetsHook,
  190. afterProcessAssets: afterProcessAssetsHook,
  191. /** @type {AsyncSeriesHook<[CompilationAssets]>} */
  192. processAdditionalAssets: new AsyncSeriesHook(["assets"]),
  193. /** @type {SyncBailHook<[], boolean>} */
  194. needAdditionalSeal: new SyncBailHook([]),
  195. /** @type {AsyncSeriesHook<[]>} */
  196. afterSeal: new AsyncSeriesHook([]),
  197. /** @type {SyncWaterfallHook<[RenderManifestEntry[], RenderManifestOptions]>} */
  198. renderManifest: new SyncWaterfallHook(["result", "options"]),
  199. /** @type {SyncHook<[Hash]>} */
  200. fullHash: new SyncHook(["hash"]),
  201. /** @type {SyncHook<[Chunk, Hash, ChunkHashContext]>} */
  202. chunkHash: new SyncHook(["chunk", "chunkHash", "ChunkHashContext"]),
  203. /** @type {SyncHook<[Module, string]>} */
  204. moduleAsset: new SyncHook(["module", "filename"]),
  205. /** @type {SyncHook<[Chunk, string]>} */
  206. chunkAsset: new SyncHook(["chunk", "filename"]),
  207. /** @type {SyncWaterfallHook<[string, object, AssetInfo]>} */
  208. assetPath: new SyncWaterfallHook(["path", "options", "assetInfo"]),
  209. /** @type {SyncBailHook<[], boolean>} */
  210. needAdditionalPass: new SyncBailHook([]),
  211. /** @type {SyncHook<[Compiler, string, number]>} */
  212. childCompiler: new SyncHook([
  213. "childCompiler",
  214. "compilerName",
  215. "compilerIndex"
  216. ]),
  217. /** @type {SyncBailHook<[string, LogEntry], true>} */
  218. log: new SyncBailHook(["origin", "logEntry"]),
  219. /** @type {SyncWaterfallHook<[WebpackError[]]>} */
  220. processWarnings: new SyncWaterfallHook(["warnings"]),
  221. /** @type {SyncWaterfallHook<[WebpackError[]]>} */
  222. processErrors: new SyncWaterfallHook(["errors"]),
  223. /** @type {HookMap<SyncHook<[Partial<NormalizedStatsOptions>, CreateStatsOptionsContext]>>} */
  224. statsPreset: new HookMap(() => new SyncHook(["options", "context"])),
  225. /** @type {SyncHook<[Partial<NormalizedStatsOptions>, CreateStatsOptionsContext]>} */
  226. statsNormalize: new SyncHook(["options", "context"]),
  227. /** @type {SyncHook<[StatsFactory, NormalizedStatsOptions]>} */
  228. statsFactory: new SyncHook(["statsFactory", "options"]),
  229. /** @type {SyncHook<[StatsPrinter, NormalizedStatsOptions]>} */
  230. statsPrinter: new SyncHook(["statsPrinter", "options"]),
  231. get normalModuleLoader() {
  232. return getNormalModuleLoader();
  233. }
  234. });
  235. }
  236. }

webpack 构建流程

webpack 的编译都按照下面的钩子调用顺序执行
webpack 原理及源码 - 图5

准备阶段

Compiler Hooks

  • 流程相关
    • before-run、run
    • before-complie、complier、after-complie
    • make
    • emit、after-emit
    • done
  • 监听相关

    • watch-run
    • watch-close

      WebpackOptionsApply

      将所有的配置 options 参数转换成 Webpack 内部插件
      使用默认插件列表
  • output.library—> LibraryTemplatePlugin

  • externals—>ExternalsPlugin
  • devtool—>EvalDevtoolModulePlugin、SourceMapDevToolPlugin
  • AMDPluginCommonJSPlugin
  • RemoveEmptyChunksPlugin

    Compilation

    Compiler 调用 Compilation 生命周期方法

  • addEntry —> addModuleChain

  • finish(上报模块错误)
  • seal

    模块构建阶段

    模块构建阶段:make ——> seal

Compilation Hooks

  • 模块相关
    • build-module
    • failed-module
    • succeed-module
  • 资源生成相关
    • seal、after-seal
    • optimize、optimize-modules-basic、optimize-modules-advanced
    • after-optimize-modules
    • after-optimize-chunks
    • after-optimize-tree
    • optimize-chunk-modules-basic、optimize-chunk-modules-advanced
    • after-optimize-chunk-modules
    • optimize-modules、chunk-order
    • before-module、chunk-ids
    • optimize-module、after-optimize-module、chunk-ids
    • before-hash、after-hash
  • 优化相关

    ModuleFactory

    image.png

    Module

    image.png
  1. NormalModule

构建

  • 使用 loader-runner 运行 loaders
  • 通过 Parser 解析(内部是使用 acron)
  • ParserPlugins 添加依赖

    Chunk 生成算法

  1. webpack 先将 entry 中对应的 module 都生成一个新的 chunk
  2. 遍历 module 的依赖列表,将依赖的 module 也加入到 chunk 中
  3. 如果一个依赖 module 是动态引入的模块,那么就会根据这个 module 创建一个新的 chunk,继续遍历依赖
  4. 重复上面的过程,直到得到所有的 chunks

文件生成阶段

seal 阶段 —> emit 阶段

实现一个简易的 webpack

可以实现 ES6 转 ES5

  • 通过 babylon 生成 AST
  • 通过 babel-core、@babel/preset-env、babel-preset-env 将 AST 重新生成代码

可以分析模块之间的依赖关系

  • 通过 babel-traverse 的 ImportDeclaration 方法获取依赖属性

生成 JS 文件可以在 浏览器运行

前置知识

AST

AST(abstract syntax tree):抽象语法树,是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。

AST 在线转换

开始实现

simplePack 的 github 地址