[
  {
    "path": "101-110/commit-101-110.md",
    "content": "# 101 - d1fe471 新增id映射到请求路径\n\n允许`resolver`拥有`idToRequest`方法。\n\n```typescript\nidToRequest: (id: string) => {\n      for (const r of resolvers) {\n        const request = r.idToRequest && r.idToRequest(id)\n        if (request) return request\n      }\n}\n```\n\n## 影响范围？\n\n在`serverPluginModules.ts`中的`rewriteImports`，会利用`idToRequest`作为**模块改写的路径**。\n\n```typescript\nif (/^[^\\/\\.]/.test(id)) { // import lodash from 'lodash'\n            const rewritten = resolver.idToRequest(id) || `/@modules/${id}`\n            s.overwrite(start, end, rewritten)\n            hasReplaced = true\n            debugImportRewrite(`    \"${id}\" --> \"${rewritten}\"`)\n}\n```\n\n## 可能引起的BUG？\n\n```typescript\n# serverPluginModules\n// handle /@modules/:id requests\nconst moduleRE = /^\\/@modules\\//\n```\n\n无法正确解析模块了。\n\n\n\n# 102 - fe1ef5a 修复传入的路径类型\n\n修复`handleJSReload`：\n  原本传入的是`filePath`，真正需要的是`publicPath`。\n\n\n\n# 103 - ab2610f 使`js`与`vue`的`reload`方法传入的`timeStamp`参数为可选选项\n\n在重载方法中使时间戳成为可选的。\n\n```typescript\nexport type ViteWatcher = FSWatcher & {\n  handleVueReload: (file: string, timestamp?: number, content?: string) => void\n  handleJSReload: (file: string, timestamp?: number) => void\n}\n```\n\n\n\n# 104 - 3fce891 @xxx自动转变为/@xxx\n\n顺带整理一下改写`import`语句的代码。\n\n把`resolver.idToRequest.defaultIdToRequest`的@id字符转为'/@id'。\n\n把`vue组件`的`import`语句`/@hmr`改写为`@hmr`。\n\n1. 一个`vue组件`，经过`serverPluginVue.ts`处理，变成`import { updateStyle } from '@hmr'`。\n2. 经过`serverPluginServe.ts`中的`resolver.idToRequest.defaultToRequest`处理，变成`import { updateStyle } from '/@hmr'`\n\n\n\n# 105 - c4a2d40 因104修改reademe文档\n\n### HMR所做的替换\n\n- `*.vue` files come with HMR out of the box.\n\n- For `*.js` files, a simple HMR API is provided:\n\n  ```js\n  import { foo } from './foo.js'\n  import { hot } from '@hmr' // 以前是/@hmr\n  \n  foo()\n  \n  hot.accept('./foo.js', ({ foo }) => {\n    // the callback receives the updated './foo.js' module\n    foo()\n  })\n  ```\n\n## `hmr`对`js`文件无效\n\n原因很简单，`importerMap`的键为`filePath`，而`handleJSReload`使用了`publicPath`来获取`importer`。\n\n> 再次提醒importer为引入文件，importee为被引入文件\n\n\n\n# 106 - 4c354ca hot路径修改\n\n```typescript\n// 以前：\nhot.accept(\"/fc.js\", \"\\\\foo.js\", ({ foo }) => {\n  // the callback receives the updated './foo.js' module\n  foo()\n})\n\n// 现在\nhot.accept(\"/fc.js\", \"E:/foo.js\", ({ foo }) => {\n  // the callback receives the updated './foo.js' module\n  foo()\n})\n```\n\n`parseAcceptedDeps`:\n\n![](./1.png)\n\n这里我有些小失误，没有去讲解hot的使用问题，我在51-60commit重新补充了`handleJSReload`相关的。`parseAcceptedDeps`可以把他当作收集依赖与`hot.accept`表达式的改写，例如上面的例子`foo.js`依赖`fc.js`文件。\n\n\n\n# 107 - e42d74d `serverPluginsVue`错误提示\n\n添加错误提示（`template script style`）\n\n\n\n# 108 - 4ac0801 index.html重写缓存\n\n对于`index.html`文件，`rewriteCache`的`key`变成`ctx.body`。\n\n我个人觉得改不改一样，对于功能上，也许更符合语义吧... 但`rewriteCache`的体积会变得更大，因为没有地方删除以前的内容（除非是超出LRU max）。\n\n\n\n# 109 - ea97e3b 修复`build.ts`路径处理bug\n\n由于之前把@xxx弄成自动的，在`build`环境下，就不是自动的了。\n\n所以`vitePlugin`(rollup插件)的`resolveId`需要改写。\n\n\n\n# 110 - a22472d `hmr`添加新事件`custom`\n\n整理代码命名。\n\n```typescript\n# client.ts\nconst customUpdateMap = new Map<string, ((customData: any) => void)[]>()\n\ncase 'custom':\n      const cbs = customUpdateMap.get(id)\n      if (cbs) {\n        cbs.forEach((cb) => cb(customData))\n      }\n      break\n\n\nexport const hot = {\n  on(event: string, cb: () => void) {\n    const exisitng = customUpdateMap.get(event) || []\n    exisitng.push(cb)\n    customUpdateMap.set(event, exisitng)\n  }\n}\n```\n\n未能分析其作用，将在后续commit解说其作用。\n\n> 看出个大概就是服务端与client端能通过一个事件交流数据`customData`。\n"
  },
  {
    "path": "11-20/commit-11-20.md",
    "content": "# 11 - 7f207eb annotation\n\n添加测试注释（准备测试style HMR）。\n\n\n\n# 12 - 5c0f552 fix propublishOnly\n\n注释。\n\n```json\n{\n-   prepublishOnly: \"tsc\"\n+   propublishOnly: \"yarn build\"    \n}\n```\n\n\n\n# 13 - be00e79 v0.1.0发布\n\n修改旧名称 ```vds```为```vite```，包括注释，控制台输出，只是更改名称。\n\n\n\n# 14 - a47c406 设置npm上传包含的文件\n\n`files` 字段用于描述我们使用 `npm publish` 命令后推送到 `npm` 服务器的文件列表，如果指定文件夹，则文件夹内的所有内容都会包含进来。我们可以查看下载的 `antd` 的 `package.json` 的`files` 字段，内容如下：\n\n```json\n\"files\": [\n  \"dist\",\n+ \"bin\"\n],\n```\n\n因为```dist```是```build```的文件，```/bin/vite.js```是启动文件，所以这一部分发布到```npm```，提供用户使用即可。\n\n\n\n# 15 - a4f093a v0.1.1发布\n\n```json\n{\n-   version: \"0.1.1\"\n+   version: \"0.1.1\" \n}\n```\n\n\n\n# 16 - d58893b  chore readme\n\n```chore: readme```，修改```readme```。\n\n\n\n# 17 - c76ca14 添加ci\n\n```yml\nversion: 2\n\ndefaults: &defaults\n  docker:\n    - image: vuejs/ci # https://hub.docker.com/r/vuejs/ci\n\nstep_restore_cache: &restore_cache\n  restore_cache:\n    keys:\n    - v1-dependencies-{{ checksum \"yarn.lock\" }}-1\n    - v1-dependencies-\n\nstep_install_deps: &install_deps\n  run:\n    name: Install Dependencies\n    command: yarn --frozen-lockfile\n\nstep_save_cache: &save_cache\n  save_cache:\n    paths:\n      - node_modules\n      - ~/.cache/yarn\n    key: v1-dependencies-{{ checksum \"yarn.lock\" }}-1\n\njobs:\n  test:\n    <<: *defaults\n    steps:\n      - checkout\n      - *restore_cache\n      - *install_deps\n      - *save_cache\n      - run: yarn test\n\nworkflows:\n  version: 2\n  ci:\n    jobs:\n      - test\n\n```\n\nsetp1: 在自身```Github```上，创建一个```public```仓库，命名为```sbuild```，并把上传这一份代码。\n\nstep2: 使用该```github```账号登录```circleci```网站，在目录列表点击```Set Up Project ```。\n\n![](./circle-view.png)\n\nstep3: 点击``Start Building``\n\n![](./circle-test.png)\n\n（失败可以不用管，这是运行test命令失败，在不同平台有些不一样）\n\n\n\n# 18 - 97de06e test: fix pupeteer on ci\n\n修复在```ci```环境下的，```puppeteer```报错问题（如上报错）。\n\n![](circle-sussess.png)\n\n\n\n# 19 - bb9baa2 使用本包，如果用户没有vue\n\n### moduleResolver.ts\n\n整理代码，如果用户本地路径没有寻找到```vue.runtime.esm-browser.js```，则从本包中读取```vue.runtime.esm-browser.js```。\n\n\n\n# 20 - 0d5a2a4 fix: vue路径 & compile-sfc路径\n\n### moduleResolver.ts\n\n```vue.runtime.esm-browser.js```的寻找方式：从cwd层级查找。\n\n```cwd```目录下的```vue```版本需要和本包中的```vue```版本一致，不然报错，且```compiler-sfc```使用本包中的```vue/compiler-sfc```。\n\n如果用户不存在```vue```包，则```compiler-sfc```与```vue.runtime.esm-browser.js```均使用本包的。\n\n这里主要是在客户端```import vue```的时候，顺带设置好```vueCompiler.ts```中需要的```compiler-sfc```。\n\n### vueCompiler.ts\n\n```typescript\nimport {\n  SFCDescriptor,\n  SFCStyleBlock,\n  SFCTemplateBlock\n} from '@vue/compiler-sfc'\n```\n\nSFC三个类型从本包取，编译功能从```moduleResolver.ts```中取```resolveCompiler```。"
  },
  {
    "path": "11-20/main.rs",
    "content": "enum SpreadsheetCell {\n        Int(i32),\n        Float(f64),\n        Text(String),\n}\n\nfn main() {\n    let row = vec![SpreadsheetCell::Int(3), SpreadsheetCell::Float(10.12), SpreadsheetCell::Text(String::from(\"String text\"))];\n    for i in &row {\n        println!(\"{:?}\", i);\n        match i {\n            SpreadsheetCell::Int(i2) => {\n                println!(\"{}\", i2);\n            },\n            _ => {\n                println!(\"..\");\n            }\n        };\n\n        if let SpreadsheetCell::Float(value) = i {\n            println!(\"{}\", value);\n        }\n    }\n}"
  },
  {
    "path": "111-120/commit-111-120.md",
    "content": "# 111 - 14346ee 修复`__DEV__`条件下`hot.accept`没有被正确渲染的问题\n\n```typescript\n// 错误\n__DEV__ && hot.accept('./foo.js', ({ foo }) => {\n  // the callback receives the updated './foo.js' module\n  foo()\n})\n\n// 正确\n__DEV__ && hot.accept(\"/fuck.js\", \"E:/foo.js\", ({ foo }) => {\n  // the callback receives the updated './foo.js' module\n  foo()\n})\n```\n\n原因，AST语法树中没有检测不同的类型\n\n```typescript\nconst checkStatements = (node: Statement) => {\n    if (node.type === 'ExpressionStatement') {\n      // top level hot.accept() call\n      checkAcceptCall(node.expression)\n      // __DEV__ && hot.accept()\n      if (\n        node.expression.type === 'LogicalExpression' &&\n        node.expression.operator === '&&' &&\n        node.expression.left.type === 'Identifier' &&\n        node.expression.left.name === '__DEV__'\n      ) {\n        checkAcceptCall(node.expression.right)\n      }\n    }\n    // if (__DEV__) ...\n    if (\n      node.type === 'IfStatement' &&\n      node.test.type === 'Identifier' &&\n      node.test.name === '__DEV__'\n    ) {\n      if (node.consequent.type === 'BlockStatement') {\n        node.consequent.body.forEach(checkStatements)\n      }\n      if (node.consequent.type === 'ExpressionStatement') {\n        checkAcceptCall(node.consequent.expression)\n      }\n    }\n  }\n```\n\n\n\n# 112 - 1b0b4ba 配置化构建\n\n```typescript\ninterface BuildOptions {\n  root?: string\n  cdn?: boolean\n  resolvers?: Resolver[] // 路径转换\n  srcRoots?: string[] // 资源白名单\n  rollupInputOptions?: InputOptions // rollup 配置(plugins配置顺序 高于vite所用的plugins)\n  rollupOutputOptions?: OutputOptions // rollup 输出配置(dir)\n  write?: boolean // 是否写输出文件到磁盘中，默认true\n  debug?: boolean // 开启debug后css与JS都不会被压缩，方便我们查看输出的代码，默认false\n  indexPath?: string // 入口文件，默认index.html\n}\n```\n\n补充几个知识：\n\n1. `transform`的类型是`sequential | async`，如果多个插件实现了相同的钩子函数，那么会串式执行，按照使用插件的顺序从头到尾执行，如果是异步的，会等待之前处理完毕，在执行下一个插件。\n2. `resolveId`和`load`的类型是`async, first`，如果多个插件实现了相同的钩子函数，那么会串式执行，从头到尾，但是，如果其中某个的返回值不是`null`也不是`undefined`的话，会直接终止掉后续插件。\n\n> 中文翻译转载了这篇[文章](https://www.cnblogs.com/yangzhuxian/p/13371637.html)\n\n## 我所认为的BUG\n\n发现尤大没有把`input`配置为`indexPath`，难道是故意的(一定是漏了，如果我配置了`indexPath`，那`js`与其它`dom`不同步了)？\n\n```typescript\n// indexPath被拿去分析<script></script>的内容了\n\nconst bundle = await rollup({\n    input: path.resolve(root, 'index.html')\n})\n```\n\n\n\n# 113 - 56815ea 整理build的代码\n\n整理代码。\n\n> 现在write仅决定是否写入磁盘的操作，之前还决定是否分析rollup的输出。\n>\n\n\n\n# 114 - fa4c91b readme\n\n现在可以配置化了。需要支持的功能依旧是`source map`。顺带还解析了一下`Vite`为什么叫`Vite`。\n\n## Trivia\n\n[vite](https://en.wiktionary.org/wiki/vite) 是法语快速的首发音 `/vit/`.\n\n\n\n# 115 - 48f2459 修复深层次的路径BUG\n\n```typescript\n// recursive: true，创建文件夹，无论是否存在/a或/a/b\n\n// filepath = '/a/b/c'\nawait fs.mkdir(path.dirname(filepath), { recursive: true })\n```\n\n\n\n# 116 - d9a0798 调整`css`文件名称 - build\n\n去除构建的配置`indexPath`，自动寻找`root`下的`index.html`。\n\n整理代码，当没有`generatedIndex`就不再触发`inject`的相关操作。\n\n## `cssExtractPlugin`(`rollupPlugin`)新增`generateBundle`\n\n利用`generateBundle`来生成`css`文件，同时可以使`css`文件名称可配置化。\n\n```typescript\nconst cssExtractPlugin: Plugin = {\n    name: 'vite-css',\n    transform(code: string, id: string) {\n      if (id.endsWith('.css')) {\n        styles.set(id, code)\n        return '/* css extracted by vite */'\n      }\n    },\n\n    async generateBundle(_options, bundle) {\n      // finalize extracted css\n      styles.forEach((s) => {\n        css += s\n      })\n      // minify with cssnano\n      if (!debug) {\n        css = (\n          await require('postcss')([require('cssnano')]).process(css, {\n            from: undefined\n          })\n        ).css\n      }\n\n      bundle[cssFileName] = {\n        isAsset: true,\n        type: 'asset',\n        fileName: cssFileName,\n        source: css\n      }\n    }\n  }\n```\n\n`generateBundle`:\n  类型：`(options: OutputOptions, bundle: { [fileName: string]: AssetInfo | ChunkInfo },isWrite: boolean) => void`\n  钩子类型：`async`, `parallel`\n  在`bundle.generate()` 后触发，`bundle.write()`前触发。\n\n ```typescript\n // AssetInfo\n {\n   fileName: string,\n   name?: string,\n   source: string | Uint8Array,\n   type: 'asset',\n }\n ```\n\n\n\n# 117 - 38fd349 去除rollup的`preserveEntrySignatures`\n\n去除`preserveEntrySignatures`，重新使用`export`的代码输出。\n\n[字段解释](https://github.com/Kingbultsea/vite-analysis/blob/d71db28f2e4bdcecdaa4fc6ad311820e6dc81427/commit-61-70/commit-61-70.md#rollup%E9%85%8D%E7%BD%AEpreserveentrysignatures%E4%B8%BAfalse)\n\n\n\n# 118 - e290349 build打包多出口配置\n\n封装打包操作，遍历`rollupOutputOptions`，调用`generator`。\n\n```typescript\nawait bundle.generate({\n      dir: outDir,\n      format: 'es',\n      ...options // OutputOptions[]\n})\n```\n\n> 注意哦，这里和rollup配置无关。\n\n\n\n# 119 - 4bc3035 log静音\n\n添加`slient`配置字段，决定是否输出`write`写入的操作。\n\n同时还删除了一些Log。\n\n\n\n# 120 - ddf2d26 添加`window.__DEV__`\n\n开发环境才会有。\n\n```html\n<div id=\"app\"></div>\n233\n\n<script>window.__DEV__ = true</script>\n<script type=\"module\" src=\"/main.js\">\n  import vue from '/@modules/vue'\n  import a from './haha/h.js'\n  console.log(a);\n    console.log('123')\n</script>\n```\n"
  },
  {
    "path": "121-130/commit-121-130.md",
    "content": "# 121 - 1a26b7a 推断正确的构建结果\n\n判断传入build的类型，输出对应的类型。\n\n```typescript\ninterface SingleBuildOptions extends BuildOptionsBase {\n  rollupOutputOptions?: OutputOptions\n}\n\ninterface MultiBuildOptions extends BuildOptionsBase {\n  rollupOutputOptions?: OutputOptions[]\n}\n\nexport async function build(options: SingleBuildOptions): Promise<BuildResult>\nexport async function build(options: MultiBuildOptions): Promise<BuildResult[]>\n```\n\n> 想返回特定情况下的类型，可以多次写一个方法。\n\n\n\n# 122 - f5c6699 v0.7.0\n\nrelease v0.7.0\n\n\n\n# 123 - de67bc6 `changelog`\n\n# [0.7.0](https://github.com/vuejs/vite/compare/v0.6.1...v0.7.0) (2020-04-29)\n\n### Bug Fixes\n\n- 修复写入的情况下，深路径文件的BUG ([48f2459](https://github.com/vuejs/vite/commit/48f2459444fd2affa053ad5857cb8bd325ea2af6))\n\n### Features\n\n- 支持`__DEV__`\n- 在构建的情况下，支持修改 `cssFileName`  ([d9a0798](https://github.com/vuejs/vite/commit/d9a0798b0d8746a816ac516bd4267a409fb82c16))\n- 允许通过选项自定义构建 ([1b0b4ba](https://github.com/vuejs/vite/commit/1b0b4ba340b5d552abd7fa0457f9b2de55fc1647))\n- 允许插件发送自定义`hmr`事件 ([a22472d](https://github.com/vuejs/vite/commit/a22472d35718d08b4a947d064c82d645cfd49349))\n- 支持省略`.js`扩展名 ([d00523f](https://github.com/vuejs/vite/commit/d00523f0efbc4453e31b138ca508d7d5d2479e34))\n\n\n\n# 124 - a0053a0 允许配置`rollup-plugin-vue`\n\n新增`rollupPluginVueOptions`选项。\n\n[rollup-plugin-vue](https://rollup-plugin-vue.vuejs.org/options.html)\n\n\n\n# 125 - 302980c debug -> minify\n\n把`debug`字段改成`minify`。原因是`debug`所做的事情是压缩代码，所以改成`minify`更贴切。\n\n\n\n# 126 - 5524e44 拆分`serverPluginModule`\n\n`serverPluginModule.ts`拆分为：`serverPluginModuleRewrite.ts`、`serverPluginModuleResolve.ts`。\n\n`serverPluginModuleRewrite.ts`: 改写`import`、`index.html`。\n\n`serverPluginModuleResolve.ts`: 发送模块资源、`vue包`。\n\n`serverPluginServe.ts`更名为`serverPluginServerStatic.ts`\n\n\n\n# 127 - d4ccd15 readme\n\n新增Build的文档示例\n\n#### Build\n\n```js\nconst { build } = require('vite')\n\n;(async () => {\n  // All options are optional.\n  // check out `src/node/build.ts` for full options interface.\n  const result = await build({\n    rollupInputOptions: {\n      // https://rollupjs.org/guide/en/#big-list-of-options\n    },\n    rollupOutputOptions: {\n      // https://rollupjs.org/guide/en/#big-list-of-options\n    },\n    rollupPluginVueOptions: {\n      // https://github.com/vuejs/rollup-plugin-vue/tree/next#options\n    },\n    root: process.cwd(),\n    cdn: false,\n    write: true,\n    minify: true,\n    silent: false\n  })\n})()\n```\n\n\n\n# 128 - a084cf2 重构迁移代码\n\n把`serverPluginModuleRewrite.ts`中改写引入了`@hmr` `import`的功能，抽离给`serverPluginHMR.ts`。\n\n```typescript\nexport const hmrBoundariesMap: HMRStateMap = new Map()\nexport const importerMap: HMRStateMap = new Map()\nexport const importeeMap: HMRStateMap = new Map()\n\n// 构建路径 也被迁移到serverPluginHMR.ts\n```\n\n迁移的目的是因为，`hmrBoundariesMap`是给`reloadJS`使用的。\n\n\n\n# 129 - a084cf2 chore合并`Import`语句\n\n等同优化代码。\n\n\n\n# 130 - b0122b8 readme和`es-dev-server`的区别\n\n## How is This Different from [es-dev-server](https://open-wc.org/developing/es-dev-server.html)?\n\n`es-dev-server` 是一个伟大的项目，在早期重构`vite`时，我们确实从中获得了一些灵感。也就是说，这就是为什么`vite`与`es-dev-server`不同，以及为什么我们不只是将`vite`作为`es-dev-server`的中间件来实现:\n\n- `vite` 支持热模块更换, 在不重新加载页面的情况下通过更新模块。 这在开发模式中有着本质性的区别。 `es-dev-server` 内部结构有点不透明，无法通过中间件很好地工作。\n- `vite`皆在成为一个拥有开发和构建功能的单一程序。 你可以在不配置任何东西的情况下，使用`vite`来打包代码。\n- `vite` 需要原生 ES 模块导入。它不打算增加对旧版浏览器的支持。\n\n"
  },
  {
    "path": "131-140/commit-131-140.md",
    "content": "# 131 - 30ab444 `hmr.accept`支持调用本身\n\n输入：\n\n```typescript\n// # foo.js\n\nimport { hot } from '@hmr'\n\nexport const count = 1\n\nhot.accept(newModule => {\n  console.log('updated: count is now ', newModule.count)\n})\n```\n\n输出：\n\n```typescript\n// foo.js\n\nexport const count = 1\n\nhot.accept(\"/foo.js\", \"/foo.js\", newModule => {\n  console.log('updated: count is now ', newModule.count)\n})\n```\n\n新增代码：\n\n* `accept`的`callback`默认值`() =>  {}`，以防止`hot.accept('')`\n* AST分析树，如果第一个参数类型是`FunctionExpression`，添加上`/foo.js`作为第二个参数。\n\n> 对AST树有兴趣的，可以仔细研究一下。`mozila`有AST类型文档。使用的包`@babel/parser`，类型包`'@babel/types'`，最后使用`magic-string`替换。\n\n\n\n# 132 - 4ce94b6 v0.8.0\n\nrelease v0.8.0\n\n\n\n# 133 - d609620 changelog\n\n# [0.8.0](https://github.com/vuejs/vite/compare/v0.7.0...v0.8.0) (2020-04-30)\n\n### Features\n\n- 构建时，允许配置`rollupPluginVueOptions` ([a0053a0](https://github.com/vuejs/vite/commit/a0053a0eccd2659da685427ac3057cf5b436df80))\n- `process.env.NODE_ENV` ([d4ccd15](https://github.com/vuejs/vite/commit/d4ccd154f54f71fb02e746924f9811d3a0e61a8f))\n- `hmr.accept`支持调用本身 ([30ab444](https://github.com/vuejs/vite/commit/30ab444bd28b47eec1cf070a3c41116e8e9c64be))\n\n\n\n# 134 - b70cd66 [#24](https://github.com/vitejs/vite/pull/24) 语法调整\n\n语法调整\n\n\n\n# 135 - 7d5c099 [#26](https://github.com/vitejs/vite/pull/26) 修复错字\n\n修复了在源代码中发现的一些错字\n\n\n\n# 136 - 770e558 更新`rollup-plugin-vue` and `vue`包\n\n\n\n![](./pkg.png)\n\n\n\n# 137 - 7b126af fix `resolver ensurejs` check\n\n`ensureJS`当有请求参数的时候尝试补充`.js`拓展名称，不存在则不做任何处理地返回。\n\n```typescript\nimport { statSync } from 'fs'\n\nrequestToFile: (publicPath) => {\n      let resolved: string | undefined\n      for (const r of resolvers) {\n        const filepath = r.requestToFile(publicPath, root)\n        if (filepath) {\n          resolved = filepath\n          break\n        }\n      }\n      if (!resolved) {\n        resolved = defaultRequestToFile(publicPath, root)\n      }\n      resolved = ensureJs(resolved)\n      return resolved\n}\n\n// \nconst ensureJs = (id: string) => {\n  // 去除所有参数  \n  const cleanId = id.replace(queryRE, '')\n  \n  // 有参数的情况\n  if (!/\\.\\w+$/.test(cleanId)) {\n    // try to see if there is actually a corresponding .js file on disk.\n    // if not, return the id as-is\n    try {\n      statSync(cleanId + '.js')\n    } catch (e) {\n      return id\n    }\n     \n    // 添加js参数  \n    const queryMatch = id.match(queryRE)\n    const query = queryMatch ? queryMatch[0] : ''\n    return cleanId + '.js' + query\n  }\n    \n    \n  return id\n}\n```\n\n## statSync\n\n同步版本的`fs.stat`，在此作用为查看`.js`文件是否存在，不存在则返回本身`id`。\n\n\n\n# 138 - 704fb84 v0.8.1\n\nrelease v0.8.1\n\n\n\n# 139 - 5ec60b1 changelog\n\n## [0.8.1](https://github.com/vuejs/vite/compare/v0.8.0...v0.8.1) (2020-04-30)\n\n### Bug Fixes\n\n- 解决`resolver` 里的 `ensurejs` 检测问题 ([3a3442f](https://github.com/vuejs/vite/commit/3a3442f0b95873dd2a6869b00d8ac19b74d650a3))\n\n\n\n# 140 - 54366c6 readme 接下来要做的事情\n\n## TODOs\n\n- 支持`import` `.css` 和 `.json`\n- 公共路径处理（话说这个我不知道是什么... `Public path`我认为是请求路径，需要处理不同的类型，现在仅支持`js`）\n- 支持`config`配置文件\n- 自动引入`postcss config`文件\n- `Vue`文件的`source map`\n\n"
  },
  {
    "path": "141-150/commit-141-150.md",
    "content": "# 141 - 6125ee9 拼写错误[#27](https://github.com/vitejs/vite/pull/27)\n\n[#27](https://github.com/vitejs/vite/pull/27)\n\n\n\n# 142 - d27944c 拆分测试[#28](https://github.com/vitejs/vite/pull/28)\n\n拆分测试功能，方便后续使用`test.skip`来进行TDD。\n\n```typescript\ndescribe('my suite', () => {\n  test('my only true test', () => {\n    expect(1 + 1).toEqual(2);\n  });\n  // Should fail, but isn't even run\n  test.skip('my only true test', () => {\n    expect(1 + 1).toEqual(1);\n  });\n});\n```\n\n> TDD是**测试驱动开发**（Test-Driven Development）的英文简称，是敏捷开发中的一项核心实践和技术，也是一种设计方法论。TDD的原理是在开发功能代码之前，先编写单元测试用例代码，测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践，但不只适用于[XP](https://baike.baidu.com/item/XP/776028)（Extreme Programming），同样可以适用于其他开发方法和过程。\n\n\n\n# 143 - f164e46 update rollup-plugin-vue\n\n![](./pkg.png)\n\n\n\n# 144 - 3ff579c vite命令支持布尔值\n\n支持使用`vite`配置` --flag=false`。\n\n```typescript\n// bin/vite.js\n\nObject.keys(argv).forEach((key) => {\n  if (argv[key] === 'false') {\n    argv[key] = false\n  }\n})\n```\n\n\n\n# 145 - 2677c93 关闭src转换\n\n编译vue文件中的`<template>`，关闭` trasnformAssetUrls`选项。\n\nhttps://vue-loader.vuejs.org/zh/options.html#transformasseturls\n\n```typescript\n// Transform asset urls found in the template into `require()` calls\n  // This is off by default. If set to true, the default value is\n  // {\n  //   audio: 'src',\n  //   video: ['src', 'poster'],\n  //   source: 'src',\n  //   img: 'src',\n  //   image: ['xlink:href', 'href'],\n  //   use: ['xlink:href', 'href']\n  // }\ntransformAssetUrls?: AssetURLOptions | boolean\n```\n\n## 深入了解一下\n\n我们查看关闭前的template转换成了什么？\n\n```typescript\nimport { createVNode as _createVNode, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \"/@modules/vue\"\nimport _imports_0 from './assest/pkg.png'\n\n\nconst _hoisted_1 = _createVNode(\"img\", { src: _imports_0 }, null, -1 /* HOISTED */)\n\nexport function render(_ctx, _cache) {\n  const _component_Child = _resolveComponent(\"Child\")\n\n  return (_openBlock(), _createBlock(_Fragment, null, [\n    _hoisted_1,\n    _createVNode(\"button\", {\n      class: \"foo\",\n      onClick: _cache[1] || (_cache[1] = $event => (_ctx.count++))\n    }, _toDisplayString(_ctx.count) + \"123\", 1 /* TEXT */),\n    _createVNode(_component_Child)\n  ], 64 /* STABLE_FRAGMENT */))\n}\n```\n\n报错：\n\n> pkg.png:1 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of \"image/png\". Strict MIME type checking is enforced for module scripts per HTML spec.\n\n意味着我们不能使用`import`一个`image/png`类型的文件。\n\n\n\n我们查看关闭后：\n\n```typescript\nimport { createVNode as _createVNode, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \"/@modules/vue\"\n\nconst _hoisted_1 = _createVNode(\"img\", { src: \"./assest/pkg.png\" }, null, -1 /* HOISTED */)\n\nexport function render(_ctx, _cache) {\n  const _component_Child = _resolveComponent(\"Child\")\n\n  return (_openBlock(), _createBlock(_Fragment, null, [\n    _hoisted_1,\n    _createVNode(\"button\", {\n      class: \"foo\",\n      onClick: _cache[1] || (_cache[1] = $event => (_ctx.count++))\n    }, _toDisplayString(_ctx.count) + \"123\", 1 /* TEXT */),\n    _createVNode(_component_Child)\n  ], 64 /* STABLE_FRAGMENT */))\n}\n```\n\n一切恢复正常，图片可以正常显示了。\n\n为什么会有transformAssetUrls这个选项？是webpack需要使用到的，转换后webpack会再经过自身插件的转换。\n\n> 啊哈，所以webpack就是没vite快！\n\n\n\n# 146 - 93167d6 调整todo 新增相对路径处理\n\n## TODOs\n\n- 相对路径和基本公共路径处理\n- 自动加载`post css`配置\n- 支持 `.css` 和 `.json`\n- 配置文件支持 (custom import maps)\n- Vue file source maps\n\n是不是尤大看到资源处理还没处理，需要解决相对路径的处理？\n\n\n\n# 147 - 9af9ec1 客户端支持wss [#31](https://github.com/vitejs/vite/pull/31)\n\n检测`location.protocol`是不是`https`，是则自动开启`wss`\n\n> WS（WebSocket ）是不安全的 ，容易被窃听，因为任何人只要知道你的ip和端口，任何人都可以去连接通讯。\n> WSS（Web Socket Secure）是WebSocket的加密版本。\n\n[443端口和80端口](https://zhuanlan.zhihu.com/p/99950177)\n\n> 网络端口：计算机与外界通讯交流的出口\n\n# 为什么`node`服务不用任何设置就可以使用wss？\n\n![](./yyx.png)\n\n尤大说，目前还不支持`wss`。\n\n[【NODE】用WS模块创建加密的WS服务(WSS)](https://luojia.me/2015/07/21/%E3%80%90node%E3%80%91%E7%94%A8ws%E6%A8%A1%E5%9D%97%E5%88%9B%E5%BB%BA%E5%8A%A0%E5%AF%86%E7%9A%84ws%E6%9C%8D%E5%8A%A1wss/)\n\n> 和https设置是一样的\n\n## 最后被回滚了\n\n![yyx2](./yyx2.png)\n\n原因使用https也不会使用wss服务（？？htttps必须使用wss的）。\n\n关于这个我十分懵逼... 等`--https`支持了，后续再看看吧。\n\n\n\n# 148 - 5d7ac46 处理相对资源路径+base64支持\n\n## package.json\n\n```json\n{\n+    \"koa-send\": \"^5.0.0\"\n}\n```\n\n新增静态文件服务中间件。\n\n[koa-send是什么](https://www.cnblogs.com/jiasm/p/9527536.html)\n\n## node/utils.ts\n\n新增`isStaticAsset`检测是否属于（常见）静态文件的方法。\n\n```typescript\nexport const scriptRE = /<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gm\n\nconst imageRE = /\\.(png|jpe?g|gif|svg)(\\?.*)?$/\nconst mediaRE = /\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/\nconst fontsRE = /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/i\n\nexport const isStaticAsset = (file: string) => {\n  return imageRE.test(file) || mediaRE.test(file) || fontsRE.test(file)\n}\n```\n\n## node/resolveVue.ts\n\n区分浏览器版本（browser）和构建版本（bundler）的`vue`。\n\n> browser是一个完整性的vue包（单文件），bundler是一个带有import的包（就是不会把所有东西都打包成一个文件，但是会从import引入）。\n>\n> 两者都包含编译器。\n\n```typescript\ninterface ResolvedVuePaths {\n  browser: string // esm.browser路径\n  bundler: string // esm.bundler路径\n  version: string // require('vue/package.json').version\n  hasLocalVue: boolean // 是否有本地vue，没有则表示其他的路径均从vite的依赖获取\n  compiler: string\n  cdnLink: string\n}\n```\n\n## node/buildPluginAsset.ts 新增base64\n\n对于`css`里面的`url(\"\")`，打包的静态资源除了`svg`以外，都会被转换成`base64`。\n\n```typescript\nexport const getAssetPublicPath = async (id: string, assetsDir: string) => {\n  const ext = path.extname(id)\n  const baseName = path.basename(id, ext)\n  const resolvedFileName = `${baseName}.${hash_sum(id)}${ext}`\n\n  let url = slash(path.join('/', assetsDir, resolvedFileName))\n  const content = await fs.readFile(id)\n  if (!id.endsWith(`.svg`)) {\n    if (content.length < inlineThreshold) {\n      url = `data:${mime.lookup(id)};base64,${content.toString('base64')}`\n    }\n  }\n\n  return {\n    content,\n    fileName: resolvedFileName,\n    url\n  }\n}\n```\n\n## node/serverPluginServeStatic.ts\n\n引入`resolver`，用于处理用户自定义路径(`resolver.requestToFile(ctx.path)`)。\n\n```typescript\n// 新增\napp.use((ctx, next) => {\n    const redirect = resolver.requestToFile(ctx.path)\n    if (!redirect.startsWith(root)) {\n      // resolver解析项目根目录之外的文件，\n      // 手动发送到此处\n      return send(ctx, redirect, { root: '/' })\n    }\n    return next()\n})\n```\n\n### 为什么有了koa-static还要用send?\n\n啊哈，主要是因为`send`好用。\n\n在这里的作用是处理`koa-static`没有的服务，比如项目外的路径。\n\n## node/serverPluginVue.ts\n\n# [3.0.0-beta.9](https://github.com/vuejs/vue-next/compare/v3.0.0-beta.8...v3.0.0-beta.9) (2020-05-04)\n\n把`transformAssetUrl: false`更改为`transformAssetUrlsBase: path.posix.dirname(publicPath)`。\n\n然后我寻找`transformAssetUrlsBase`，`vue-next beta.9`的时候被合并到`transformAssetUrl`，也意味着后续`vite`会修改过来（雀氏没有了）。\n\n**这块不是为了build的时候可以转换为Import资源，而是交给rollup-plugin-vue处理的，留坑MARK**。\n\n### Bug Fixes\n\n- **compiler:** bail strigification on runtime constant expressions ([f9a3766](https://github.com/vuejs/vue-next/commit/f9a3766fd68dc6996cdbda6475287c4005f55243))\n- **transitionGroup:** fix transition children resolving condition ([f05aeea](https://github.com/vuejs/vue-next/commit/f05aeea7aec2e6cd859f40edc6236afd0ce2ea7d))\n\n### Features\n\n- **compiler-sfc:** support transforming absolute asset urls ([6a0be88](https://github.com/vuejs/vue-next/commit/6a0be882d4ce95eb8d8093f273ea0e868acfcd24))\n\n### BREAKING CHANGES\n\n- **compiler-sfc:** `@vue/compiler-sfc`'s `transformAssetUrlsBase` option has been removed. It is merged into `trasnformAssetUrls` which now also accepts the format of\n\n  ```typescript\n  {\n    base?: string\n    includeAbsolute?: string\n    tags?: { [name: string]: string[] }\n  }\n  ```\n\n## 关于构建的改动（新增资源处理、整理plugins）\n\n#### 新增4个文件(`rollup-plugin`)：\n\n* `node/buildPluginAsset.ts`: 修改符合`isStaticAsset`的文件为`hash`路径，并存入一个Map，后续使用`generateBundle`改写对应`hash`路径的`source`。\n\n  ```typescript\n  import a from 'a.png'\n  \n  // a.png 被转换为\n  export default 'a.bf716c.png'\n  \n  // 后续a.bf716c.png，bundle修改为资源类型\n  bundle['a.bf716c.png'] = {\n      isAsset: true,\n      type: 'asset',\n      fileName,\n      source: await fs.readFile('a.png')\n  }\n  ```\n\n* `node/buildPluginCss.ts`:  对应`build`旧的`cssExtractPlugin`，`postcss`处理后输出`bundle`资源。\n\n* `node/buildPluginHtml.ts`: 把旧的`vitePlugin`抽离处理`html`功能独立出来，分析`<script>`，返回标签内容，入口的处理。\n\n* `buildPluginResolve.ts`: 旧的`vitePlugin`，处理vue包，`resolver`路径转换(在这里处理了，后续的`plugins`不用单独处理)。\n\n#### 单一出口。\n\n去除`MultiBuildOptions`，意味着之前封装成的`generator`方法又被回滚了，出口只能有一个。\n\n```typescript\n// 已被去除\ninterface MultiBuildOptions extends BuildOptionsBase {\n  rollupOutputOptions?: OutputOptions[]\n}\n```\n\n\n\n# 149 - 756da62 bump vue version\n\n![](./bump.png)\n\n\n\n# 150 - f29037d 处理使用css，import css文件\n\n注意哈，处理`css`是交给`rollup-plugin-vue`处理的，[使用`transform`处理`css`](https://github.com/vuejs/rollup-plugin-vue/blob/next/src/index.ts)，这个和我们`dev`环境下是一样的。\n\n这里做的事情是**根据内容与文件名称生成一个`bundle`**，其次把公用的方法都给抽离出来了，属于整理代码。\n\n```typescript\n // vite:css\n createBuildCssPlugin(assetsDir, cssFileName, minify)\n```\n\n> 所有vue组件里面的style，都会被丢进style.css这个bundle。\n\n## rollup其实充当了什么？\n\n**rollup分析路径**，通过**钩子**可以转换路径、更改路径下的内容，最后自己定义产出`bundle`。\n\n`vite`再把`bundle`转换成文件。\n\n"
  },
  {
    "path": "151-160/151-160.md",
    "content": "# 151 - 63b4de6 处理`<script src`\n\n1. 构建模式下，使用正则匹配出`src`里面的内容`<script src=\"main.js\">`\n\n```typescript\nconst srcRE = /\\bsrc=(?:\"([^\"]+)\"|'([^']+)'|([^'\"\\s]+)\\b)/\n\n// `src=\"main.js\"`.match(srcRE) -> main.js\n// 'src=main.js'  .match(srcRE) -> main.js\n// `src='main.js'`.match(srcRE) -> main.js\n```\n\n2. 添加import \"main.js\"\n3. 与`<script>`里的内容一起合并后返回\n\n\n\n# 152 - 8ef6d4d 支持能分析按需加载类型`import` id\n\n## node/serverPluginModuleRewrite.ts\n\n```typescript\nimports.forEach(({ s: start, e: end, d: dynamicIndex }) => {\n    let id = source.substring(start, end)\n    if (dynamicIndex >= 0) {\n          console.log(id)\n          \n        \n          const literalIdMatch = id.match(/^(?:'([^']+)'|\"([^\"]+)\")$/)\n          console.log(literalIdMatch)\n          /*\n          [\n            \"'./foo.js'\",\n            './foo.js',\n            undefined,\n            index: 0,\n            input: \"'./foo.js'\",\n            groups: undefined\n          ]\n           */\n        \n        \n          if (literalIdMatch) {\n            hasLiteralDynamicId = true\n            id = literalIdMatch[1] || literalIdMatch[2]\n          }\n    }\n}\n```\n\n## 什么是按需加载？\n\n```typescript\nimport('./foo.js').then(mod => {\n  console.log(mod.default)\n})\n```\n\n## 和直接import有什么不一样？\n\nAST中返回的id会多了引号。\n\n## bug\n\n`transformAssetUrlsBase: path.posix.dirname(publicPath)`导致了错误，这部分代码已回滚到\n` transformAssetUrls: false`\n\n\n\n# 153 - 97dc7ba 支持`json`格式的引入\n\n## 构建方面\n\n引入新的rollup插件：`@rollup/plugin-json`\n\n## dev方面\n\n实际上`json`文件默认静态资源用`serverPluginServeStatic.ts`来处理，新增`serverPluginJson.ts`，目的是改写成对象。\n\n```typescript\nimport { Plugin } from './server'\nimport { readBody } from './utils'\n\nexport const jsonPlugin: Plugin = ({ app }) => {\n  app.use(async (ctx, next) => {\n    await next()\n      \n    // handle .json imports\n    if (ctx.path.endsWith('.json')) {\n      const referer = ctx.get('referer')\n      \n      console.log(referer)\n      // http://localhost:3000/Comp.vue \n      \n      // 仅在请求页面不为.html才改写\n      if (/\\.\\w+$/.test(referer) && !referer.endsWith('.html')) {\n        ctx.type = 'js'\n        ctx.body = `export default ${await readBody(ctx.body)}`\n      }\n    }\n  })\n}\n```\n\n## `json`文件有`hmr`吗？\n\n不支持，得手动刷新页面，想支持还是使用`js`对象吧。\n\n\n\n# 154 - 67b82dc 调整`node/utils.ts`\n\n新增`isImportRequest`，该方法是在`serverPluginServeStatic.ts`中抽离出来的。\n\n```typescript\nexport const isImportRequest = (ctx: Context) => {\n  const referer = ctx.get('referer')\n  return /\\.\\w+$/.test(referer) && !referer.endsWith('.html')\n}\n```\n\n暴露所有`utils.ts`的方法。\n\n```typescript\n# node/index.ts\nexport * from './server'\nexport * from './build'\n\n// before export { cachedRead, isStaticAsset } from './utils'\nexport * from './utils'\n```\n\n\n\n# 155 - a3bb973 支持在`js`中使用`import css`文件\n\n新增`serverPluginCss.ts`。\n\n1. 处理`.css`后缀文件，且不带参数`raw`\n\n2. 转换成`js`\n\n3. 被转换的`js`文件调用`updateStyle`\n\n4. 更换`link`(`updateStyle`做的事情，之前的文章有讲解)\n\n   ```typescript\n   function updateStyle(id: string, url: string) {\n     const linkId = `vite-css-${id}`\n     let link = document.getElementById(linkId)\n     if (!link) {\n       link = document.createElement('link')\n       link.id = linkId\n       link.setAttribute('rel', 'stylesheet')\n       link.setAttribute('type', 'text/css')\n       document.head.appendChild(link)\n     }\n     link.setAttribute('href', url)\n   }\n   ```\n\n   \n\n```typescript\nimport { Plugin } from './server'\nimport { isImportRequest } from './utils'\nimport { hmrClientId } from './serverPluginHmr'\nimport hash_sum from 'hash-sum'\n\nexport const cssPlugin: Plugin = ({ app }) => {\n  app.use(async (ctx, next) => {\n    await next()\n    // handle .css imports\n    // we rewrite it to JS that injects a <style> tag pointing to the same url\n    // but with a `?raw` query which returns the actual css\n    if (\n      ctx.path.endsWith('.css') &&\n      isImportRequest(ctx) &&\n      // note ctx.body could be null if upstream set status to 304\n      ctx.body &&\n      // skip raw requests\n      !ctx.query.raw\n    ) {\n      ctx.type = 'js'\n      const id = JSON.stringify(hash_sum(ctx.path))\n      const rawPath = JSON.stringify(ctx.path + '?raw')\n      ctx.body = `\nimport { updateStyle } from \"${hmrClientId}\"\\n\nupdateStyle(${id}, ${rawPath})\n`.trim()\n    }\n  })\n}\n```\n\n\n\n# 156 - 538198c 支持`import css hmr`功能\n\n## `node/serverPluginCss.ts`\n\n```typescript\n// handle hmr\n  watcher.on('change', (file) => {\n    if (file.endsWith('.css')) {\n      const publicPath = resolver.fileToRequest(file)\n      const id = hash_sum(publicPath)\n      watcher.send({\n        type: 'style-update',\n        id,\n        path: publicPath,\n        timestamp: Date.now()\n      })\n    }\n  })\n```\n\n## `hmr`事件类型\n\n更名：\n`vue-style-remove` -> `style-remove`\n\n``vue-style-update` -> `style-update`\n\n```typescript\ninterface HMRPayload {\n  type:\n    | 'vue-rerender'\n    | 'vue-reload'\n    | 'vue-style-update'\n    | 'js-update'\n    | 'style-update'\n    | 'style-remove'\n    | 'full-reload'\n    | 'custom' // 用户自定义事件 这个我们还没有深入应用\n  timestamp: number\n  path?: string\n  id?: string\n  index?: number\n  customData?: any\n}\n```\n\n\n\n# 157 - 80c5e00 v0.9.0\n\nrelease v0.9.0\n\n\n\n# 158 - 4b64c06 changelog\n\n# [0.9.0](https://github.com/vuejs/vite/compare/v0.8.0...v0.9.0) (2020-05-03)\n\n### Bug Fixes\n\n- 去除trasnformAssetUrls ([2677c93](https://github.com/vuejs/vite/commit/2677c934fdeccf8d4a2b0a6f174ee55ab001b25a))（去除了出BUG）\n- 修复resolver ensurejs 检测([7b126af](https://github.com/vuejs/vite/commit/7b126af193459da777fa0ca581e8f31d163541fa))\n\n### Features\n\n- 处理index.html的 `<script src>`  ([63b4de6](https://github.com/vuejs/vite/commit/63b4de6405e5a2e1375f8360420c7cd11fdcd665))\n- 处理 js css import hmr ([538198c](https://github.com/vuejs/vite/commit/538198c8ec795d0030a0a11c076d717a26f389a9))\n- 处理相对路径的资源 ([5d7ac46](https://github.com/vuejs/vite/commit/5d7ac468091adf2d6809e6a735990bf20b28de87))\n- 处理css内的相对urls + base64支持([f29037d](https://github.com/vuejs/vite/commit/f29037d536de415ee115d5a48ec7a7e2b785656e))\n- 支持从js里面引入css([a3bb973](https://github.com/vuejs/vite/commit/a3bb973a3c593d25ebcf74eee7b1345c4a844e9f))\n- 支持引入json文件，作为对象输出([97dc7ba](https://github.com/vuejs/vite/commit/97dc7ba8e1d77f63dd1cecfc08f2bb513b3a708f))\n- 支持输入 --flag=false via cli ([3ff579c](https://github.com/vuejs/vite/commit/3ff579c7de84787d2533ae0f1e2695900949e7d9))\n- 支持dynamic imports ([8ef6d4d](https://github.com/vuejs/vite/commit/8ef6d4d12b5fc75b137fed7258114a2c5a17101c))\n- ws protocol based on location protocol ([#31](https://github.com/vuejs/vite/issues/31)) ([9af9ec1](https://github.com/vuejs/vite/commit/9af9ec1694f1c5c09c5ce46f81b62af175997b25))（回滚）\n\n\n\n# 159 - 5efb82d readme更新todo\n\n## TODOs\n\n- 自动加载`post css`配置\n- 配置文件支持 (custom import maps)\n- Vue file source maps\n\n## 去除\n\n* 相对路径和基本公共路径处理\n* 支持 `.css` 和 `.json`\n\n\n\n# 160 - a83637e 解决html响应体为null的问题\n\n## `node/utils.ts` 没有流的情况下返回`null`\n\n```typescript\nexport async function readBody(\n  stream: Readable | Buffer | string | null\n): Promise<string | null> {\n  if (stream instanceof Readable) {\n    return new Promise((resolve, reject) => {\n      let res = ''\n      stream\n        .on('data', (chunk) => (res += chunk))\n        .on('error', reject)\n        .on('end', () => {\n          resolve(res)\n        })\n    })\n  } else {\n    return !stream || typeof stream === 'string' ? stream : stream.toString()\n  }\n}\n```"
  },
  {
    "path": "161-170/161-170.md",
    "content": "# 161 - e947303 v0.9.1\n\nrelease v0.9.1\n\n\n\n# 162 - bf1abbf changelog\n\n## [0.9.1](https://github.com/vuejs/vite/compare/v0.9.0...v0.9.1) (2020-05-03)\n\n### Bug Fixes\n\n- 现在`readBody`方法可以返回为空的内容了 ([a83637e](https://github.com/vuejs/vite/commit/a83637e82c86df43edaf28e469bec6cbf6ad8b33))\n\n\n\n# 163 - d0b896f fix `hmr`名称\n\n`vue`的`hmr`: `style-update` -> `vue-style-update`\n\n## `vue-style-update`和`style-update`有什么不一样？\n\n```typescript\ncase 'vue-style-update':\n      updateStyle(id, `${path}?type=style&index=${index}&t=${timestamp}`)\n      console.log(\n        `[vite] ${path} style${index > 0 ? `#${index}` : ``} updated.`\n      )\n      break\ncase 'style-update':\n      updateStyle(id, `${path}?raw&t=${timestamp}`)\n      console.log(`[vite] ${path} updated.`)\n\nexport function updateStyle(id: string, url: string) {\n  const linkId = `vite-css-${id}`\n  let link = document.getElementById(linkId)\n  if (!link) {\n    link = document.createElement('link')\n    link.id = linkId\n    link.setAttribute('rel', 'stylesheet')\n    link.setAttribute('type', 'text/css')\n    document.head.appendChild(link)\n  }\n  link.setAttribute('href', url)\n}\n```\n\n可以看到并没什么不一样，区别的是名称与参数。\n\n> 但是这里会造成vue hmr识别不了style，因为没有type=style。\n\n\n\n# 164 - 348a7e8 fix `isImportRequest` 取`pathname`\n\n优化，去除请求域名。\n\n```typescript\nexport const isImportRequest = (ctx: Context) => {\n  const referer = new URL(ctx.get('referer')).pathname\n  return /\\.\\w+$/.test(referer) && !referer.endsWith('.html')\n}\n```\n\n## 关于`referer`的\n\n不知道有没有小伙伴和我一样误解`referer`是浏览器当前页面的路径。\n\n如果页面A，有脚本B，脚本B请求脚本C，那么C脚本的`referer`是脚本B，利用这个特性，我们服务器就可以知道这个是脚本发出的请求还是页面发出的请求了。\n\n\n\n# 165 - 634a432 `json`引入添加`hmr`\n\n啊哈，之前就觉得`json`需要加入`hmr`，现在加入了。\n\n## `node/serverPluginJson.ts`\n\n```typescript\n// 看，被转换成js文件的好处\nwatcher.on('change', (file) => {\n    if (file.endsWith('.json')) {\n      watcher.handleJSReload(file)\n    }\n})\n\n// 然鹅 并不能用，看hmrPlugin设置的watcher\n  watcher.on('change', async (file) => {\n    const timestamp = Date.now()\n    if (file.endsWith('.vue')) {\n      handleVueReload(file, timestamp)\n    } else if (file.endsWith('.js')) { // js文件\n      handleJSReload(file, timestamp)\n    }\n  })\n```\n\n## `node/utils.ts` 添加`cleanUrl`方法\n\n```typescript\nexport const cleanUrl = (url: string) =>\n  url.replace(hashRE, '').replace(queryRE, '')\n```\n\n## `serverPluginModuleRewrite.ts` 对是否取缓存加入参数t的判断\n\n作用为修复`js` `hmr`的时候，因请求`url`没有改变，浏览器将使用缓存，导致`js`的内容为旧的问题。\n\n```typescript\nimport foo from 'foo.js?t=123'\n\n// 将不会对foo.js里面的import进行缓存\n```\n\n### 再补充一下`serverPluginModuleRewrite.ts`的功能\n\n`serverPluginModuleRewrite`在洋葱模型的最外层执行，就是执行完所有`plugin`后再执行。\n\n改写对象：1. `vueSFC组件`的`<script>`内容    2. 普通`js`的内容。\n\n当被改写对象含有参数`t`，则其脚本内的所有import都会带有参数`t`。\n\n对于其中的`import`语句，如果符合`/^[^\\/\\.]/.test(id)`，即`node_modules`的模块引入，则在路径调整为 `/@modules/${id}`（如果设置了`resolver.idToRequest`，那么以配置为准）。\n\n## BUG\n\n无法使用`js`的`hmr`功能，和路径的设置有关，比如`key`是`file path`（且是一个错误的`file path`），却用了`public path`作为键，去取谁引入了该`js`模块。\n\n出问题的地方为：\n\n```\n// save the import chain for hmr analysis\nconst importee = cleanUrl(\n  slash(path.resolve(path.dirname(importer), resolved))\n)\n\n// importee 错误\n// 如E:/foo/a.js importee却为E:/a.js\n```\n\n我为了验证出这里的加入t参数的目的，暂时手动添加了字符串修复了（**ε=( o｀ω′)ノ！！！**）。\n\n## BUG-2\n\n应该加一个白名单，不单止`js`可以`hmr`。\n\n```typescript\n  watcher.on('change', async (file) => {\n    const timestamp = Date.now()\n    if (file.endsWith('.vue')) {\n      handleVueReload(file, timestamp)\n    } else if (file.endsWith('.js')) { // js文件\n      handleJSReload(file, timestamp)\n    }\n  })\n```\n\n## 可能引起的问题\n\n像BUG-2说的，添加了白名单，但需要解决名称相同的问题（`foo.json` `foo.js`，会引起其中一个无法`hmr`）。\n\n\n\n# 166 - 96f0ee02 [#38](https://github.com/vitejs/vite/pull/38) 注释\n\n修改`node/serverPluginHmr`注释。\n\nChange the docs which `module` has been split to `serverPluginModuleRewrite` & `serverPluginModuleResolve`\n\n`modulePlugin`被分为两个`plugins`了，所以要修改注释。\n\n\n\n# 167 - f69e8f4 [#36](https://github.com/vitejs/vite/pull/36)  注释\n\n修改英文单词。\n\n\n\n# 168 - b5421e7 [#40](https://github.com/vitejs/vite/pull/40) 添加`json`和`css`的测试\n\n```typescript\ntest('json data import', async () => {\n    const jsonComp = await page.$('.json')\n    expect(await jsonComp.evaluate((e) => e.textContent)).toBe('hello world')\n})\n\ntest('import plain css', async () => {\n    const child = await page.$('.child')\n    const color = await child.evaluate((e) => {\n      return window.getComputedStyle(e).color\n    })\n    expect(color).toBe('rgb(79, 192, 141)')\n})\n\ntest('style hmr', async () => {\n    const stylePath = path.join(tempDir, 'main.css')\n    const content = await fs.readFile(stylePath, 'utf-8')\n    await fs.writeFile(\n      stylePath,\n      content.replace('color: #4fc08d', 'color: red')\n    )\n\n    const child = await page.$('.child')\n    testByPolling('rgb(255, 0, 0)', () => {\n      return child.evaluate((e) => getComputedStyle(e).color)\n    })\n})\n\n// 轮询直到更新\nasync function testByPolling(expect, poll) {\n  const maxTries = 10\n  for (let tries = 0; tries < maxTries; tries++) {\n    const actual = await poll()\n    if (actual === expect || tries === maxTries - 1) {\n      expect(actual).toBe(expect)\n    } else {\n      await timeout(200)\n    }\n  }\n}\n```\n\n\n\n# 169 - d271e59 [#41](https://github.com/vitejs/vite/pull/41)加载`postcss config`\n\n传递选项给`compileStyleAsync`处理。\n\nhttps://www.npmjs.com/package/postcss-load-config\n\n```typescript\n{\n+    \"postcss-load-config\": \"^2.1.0\"\n}\n```\n\n## `node/serverPluginVue.ts`\n\n```typescript\nimport postcssrc from 'postcss-load-config'\n\nconst result = await resolveCompiler(root).compileStyleAsync({\n    source: style.content,\n    filename: filePath,\n    id: `data-v-${id}`,\n    scoped: style.scoped != null,\n    modules: style.module != null,\n    preprocessLang: style.lang as any,\n    preprocessCustomRequire: (id: string) => require(resolve(root, id)),\n    ...loadPostCssConfig(root)\n})\n\nfunction loadPostCssConfig(root: string) {\n  const config = postcssrc.sync({}, root)\n  return {\n    postcssOptions: config.options,\n    postcssPlugins: config.plugins\n  }\n}\n```\n\n\n\n# 170 - 679e414 更新`postcss`\n\n同时更新`lockfile`。什么是`lockfile`呀？[`package.json`版本号](https://zhuanlan.zhihu.com/p/384484213)。\n\n^表示允许不修改`[major, minor, patch]`元组中最左边的非零元素的更改 。\n\n所以^7.0.27 := >=7.0.27 < 8.0.0。\n\n换句话说，有些人`yarn install`会有不同的版本出现。\n\n> ## `Lockfile`的作用\n>\n> 1、确保每次install时生成稳定的依赖树，锁定依赖和依赖的依赖的版本。\n>\n> 2、提升install的速度。`yarn`和`npm`都有一些诸如适配和提取公共依赖版本、**扁平化依赖的优化策略**，`lockfile`的存在可节省计算时间。\n>\n> ### package-lock.json\n>\n> npm从5.0版本之后默认增加lockfile，但是早期不同版本对lockfile的实现有过变更：\n>\n> 1、[5.0.x版本](https://link.zhihu.com/?target=https%3A//github.com/npm/npm/releases/tag/v5.0.0)，不管package.json怎么变，install时都会根据lock文件下载。\n>\n> 2、[5.1.0版本](https://link.zhihu.com/?target=https%3A//github.com/npm/npm/releases/tag/v5.1.0)后，npm install会无视lock文件，去下载最新的npm包。\n>\n> 3、[5.4.2版本](https://link.zhihu.com/?target=https%3A//github.com/npm/npm/releases/tag/v5.4.2)后，表现和yarn.lock一致。\n>\n> 转载自[知乎](https://zhuanlan.zhihu.com/p/260094037)\n\n```json\n{\n-    \"postcss\": \"^7.0.27\",\n+    \"postcss\": \"^7.0.28\",\n}\n```\n\n## 有没有支持`config hmr`的可能呢？\n\n已经支持啦，代码是动态的。\n\n```typescript\npostcssrc.sync({}, root)\n```\n\n"
  },
  {
    "path": "171-180/171-180.md",
    "content": "# 171 - 0187d3f 对于`js`引入的`css`也要支持`postcss config` 与 `postcss`\n\n## `node/config.ts`\n\n抽离`serverPluginVue.ts`的加载`postcss config`功能。\n\n```typescript\nimport postcssrc from 'postcss-load-config'\n\n// postcss-load-config doesn't expose Result type\ntype Result = ReturnType<typeof postcssrc> extends Promise<infer T> ? T : never\n\nlet cachedPostcssConfig: Result | null | undefined\n\nexport async function loadPostcssConfig(root: string): Promise<Result | null> {\n  try {\n    return (\n      cachedPostcssConfig || (cachedPostcssConfig = await postcssrc({}, root))\n    )\n  } catch (e) {\n    return (cachedPostcssConfig = null)\n  }\n}\n\n```\n\n[用null还是用undefinded](https://www.zhihu.com/question/479435433/answer/2057762335)\n\n# `node/serverPluginCss.ts`\n\n因为之前是通过`style-update`事件来支持`js`里引入`css`的，使用的是静态文件服务。现在我们要加载`postcss config`与`postcss`，就需要拦截该请求做处理了。\n\n```typescript\n// plain css request, apply postcss transform\n        const postcssConfig = await loadPostcssConfig(root)\n        if (postcssConfig) {\n          const css = await readBody(ctx.body)\n          try {\n            const result = await require('postcss')(\n              postcssConfig.plugins\n            ).process(css, {\n              from: resolver.requestToFile(ctx.path),\n              ...postcssConfig.options\n            })\n            ctx.body = result.css\n          } catch (e) {\n            console.error(`[vite] error applying postcss transforms: `, e)\n          }\n        }\n```\n\n> `koa-static`已经做了处理，所以才会有`ctx.body`\n\n> 目前对于构建来说，是不支持`postcss config`的。\n\n## BUG \n\n处理`css`请求，只拦截了`.css`文件，但没有拦截`.sass`文件。\n\n\n\n# 172 - c9c9c87c 添加`vue source map`功能 + todo\n\n### 新增功能点\n\n1. 提前添加`@hmr`，在client请求`.html`文件的时候，被合并到`<script>`中。\n2. 去除处理`.vue`文件`plugin`的`@hmr`。\n3. 对`SFCMain`和`SFCTemplate`添加`sourcemap`。\n\n## 关于`soucemap`\n\n[什么是sourcemap?](https://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html)\n\n交给`vue.compiler.parse`处理，并开启`souceMap`选项，得到`descriptor.script.map`和`descriptor.template.map`。\n\n`descriptor.template.map`需要再经过`vue.compileTemplate`处理，最后才可以获得`map`。\n\n最后两者的map经过`genSourceMapString`转换为字符串数据，并插入到代码中。\n\n> `soucemap`简单地说，就是收集了原本代码的所有变量名和属性名所在的位置。所以我们可以用`soucemap`还原成文件本身。\n\n### 怎么使用`soucemap`呢？\n\n报错的时候，或者debugger的时候可以查看到`soucemap`。\n\n```typescript\nfunction genSourceMapString(map: object | undefined) {\n  if (!map) {\n    return ''\n  }\n  return `\\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(\n    JSON.stringify(map)\n  ).toString('base64')}`\n}\n```\n\n> 对于构建还不支持`soucemap`功能。\n\n\n\n# 173 - 8f6aa19 修复`hmr id`\n\n修复对于添加global变量的代码，应用了错误的变量的问题。\n\n```typescript\nconst devInjectionCode =\n    `\\n<script type=\"module\">` +\n    `import \"${hmrClientPublicPath}\"\\n` + // 之前不小心用到了hmrClientId\n    `window.__DEV__ = true\\n` +\n    `window.process = { env: { NODE_ENV: 'development' }}\\n` +\n    `</script>\\n`\n\n```\n\n```html\n<!-- 前 -->\n<script type=\"module\">import \"@hmr\"\nwindow.__DEV__ = true\nwindow.process = { env: { NODE_ENV: 'development' }}\n</script>\n\n<!-- 后 -->\n<script type=\"module\">import \"/@hmr\"\nwindow.__DEV__ = true\nwindow.process = { env: { NODE_ENV: 'development' }}\n</script>\n```\n\n提前引入客户端的`hmr`，为了提高一点点的速度(之前是放在`vue SFC`里的)。\n\n\n\n# 174 - ea5eb19 添加服务启动时间\n\n我发现`vite`很多延迟加载模块的，有利性能。\n\n```typescript\n# bin/vite.js\nconst s = Date.now()\n\nrequire('debug')('vite:server')(`server ready in ${Date.now() - s}ms.`)\n```\n\n\n\n# 175 - 1e8b584 延迟加载`postcss-load-config`\n\n正如172所说的延迟加载。\n\n```typescript\nexport async function loadPostcssConfig(root: string): Promise<Result | null> {\n  if (cachedPostcssConfig !== undefined) {\n    return cachedPostcssConfig\n  }\n  try {\n    const load = require('postcss-load-config') as typeof postcssrc\n    return (cachedPostcssConfig = await load({}, root))\n  } catch (e) {\n    return (cachedPostcssConfig = null)\n  }\n}\n```\n\n\n\n# 176 - c9ffb45 build支持`postcss config`\n\n## `node/buildPluginCss`\n\n与`serverPluginCss`转换`raw`参数的`css`文件的方式一致。\n\n```typescript\n // postcss\n        const postcssConfig = await loadPostcssConfig(root)\n        if (postcssConfig) {\n          try {\n            const result = await require('postcss')(\n              postcssConfig.plugins\n            ).process(code, {\n              ...postcssConfig.options,\n              from: id\n            })\n            code = result.css\n          } catch (e) {\n            console.error(`[vite] error applying postcss transforms: `, e)\n          }\n        }\n```\n\n\n\n# 177 - b612a34 v0.10.0\n\nrelease v0.10.0\n\n\n\n# 178 - changelog\n\n# [0.10.0](https://github.com/vuejs/vite/compare/v0.9.1...v0.10.0) (2020-05-04)\n\n### Bug Fixes\n\n- 修复isImportRequest获取referer的问题([348a7e8](https://github.com/vuejs/vite/commit/348a7e88e4cd104b110eb6296f5a18fdff351d32))\n- 修复 vue style hmr ([d0b896f](https://github.com/vuejs/vite/commit/d0b896fde6502298cf8ef6c1a8bb79c8d9b1963d)), 关闭 [#37](https://github.com/vuejs/vite/issues/37)\n\n### Features\n\n- 加载自定义 postcss 配置 ([#41](https://github.com/vuejs/vite/issues/41)) ([d271e59](https://github.com/vuejs/vite/commit/d271e594a14d5c941d96d1189ffb3b7aee994f2e))\n- 支持 json hmr ([634a432](https://github.com/vuejs/vite/commit/634a4328041434434260844cf8fa95d0c3340f85))\n- 对于`js`引入的`css`支持`postcss config` 与 `postcss` ([0187d3f](https://github.com/vuejs/vite/commit/0187d3f525fd76fa9855284b23836f4c3b68952a))\n- 在构建中支持 postcss ([c9ffb45](https://github.com/vuejs/vite/commit/c9ffb452133abc65067167e0895627703dcaeb5b))\n- vue source map ([c9c9c87](https://github.com/vuejs/vite/commit/c9c9c87c855994e2f307475353c1cbb7bf9cc46a))\n\n### Performance Improvements\n\n- 延迟加载 postcss-load-config ([1e8b584](https://github.com/vuejs/vite/commit/1e8b58403e83b0835ee136de7e5c9f7f0adf03f0))\n\n\n\n# 179 - 6b36f91 bump `create-vite-app` dep versions\n\n更新`create-vite-app`所依赖的`vite`版本。\n\n> bump: 将版本号增加到新的唯一值。\n\n\n\n# 180 - 2600104 `create-vite-app v1.0.0`\n\nrelease `create-vite-app` v1.0.0\n\n"
  },
  {
    "path": "181-190/181-190.md",
    "content": "# 181 -  adea1a3 bump `create-vite-app` vue\n\nbump vue\n\n\n\n# 182 - 4b5d180 更新文档 - feature\n\n### CSS / JSON Importing\n\n你可以直接从 JavaScript 导入 `.css` 和 `.json` 文件 (当然，包括 `*.vue` 文件的 `<script>` 标签).\n\n- `.json` 文件将作为对象导出。\n- `.css`文件不会导出任何东西。导入后，在开发模式中将会被注入页面\n\n 对于CSS 和 JSON 的导入，都支持HMR。\n\n### 相对资源的URL处理\n\n你可以在你的 `*.vue` 模板中引用静态资源。样式和存`.css` 文件，使用基于资源在文件系统上的位置的相对 URL。这类似于你使用 `vue-cli` 或 webpack 的 `file-loader`。\n\n在生产版本中，引用的资源将被复制到带有散列文件名的 dist 文件夹中。\n\n### PostCSS\n\n`vite` 会在所有`*.vue`文件和纯`*.css`文件中应用Post CSS配置。你只需安装必要的插件，并在你的项目根目录中添加一个 `postcss.config.js`。\n\n请注意，如果你想在 `*vue` 文件中使用 `<style module>`，你**不需要**配置 PostCSS，因为它是开箱即用的。\n\n### CSS Pre-Processors\n\n因为 `vite` 仅针对现代浏览器, 建议在 PostCSS 插件中使用原生 CSS ，它引入了CSSWG草案功能 (e.g. [postcss-nesting](https://github.com/jonathantneal/postcss-nesting))和编写简单的、符合未来标准的 CSS。 也就是说，如果你坚持使用 CSS 预处理器，你可以安装并使用:\n\n```bash\nyarn add -D sass\n<style lang=\"scss\">\n/* use scss */\n</style>\n```\n\n注意，目前不支持通过`.js`文件来引入CSS/预处理器， 但将来可能会得到支持。\n\n\n\n# 183 - 879c829 readme 更新todo\n\n## TODOs\n\n- 公共基础路径支持。（这是啥...）\n- 支持配置文件 (custom import maps and plugins)\n- 支持TypeScript / Flow /(P)React JSX via [Sucrase](https://github.com/alangpierce/sucrase)\n\n\n\n# 184 - c2f01f6 补充浏览器支持情况\n\n## Browser Support\n\n`vite` 在开发模式中使用 [native ES module imports](https://caniuse.com/#feat=es6-module) . 生产构建还依赖于动态导入进行代码拆分 (你可以使用 [polyfilled](https://github.com/GoogleChromeLabs/dynamic-import-polyfill))。\n\n`vite` 假设你的目标是现代浏览器，因此默认情况下不会执行任何面向兼容性的代码转换。从技术角度来说，你可以通过`PostCss`配置文件添加`autoprefixer`, 或者添加必要的 `polyfills` 和`post-processing`以使你的代码能在旧版浏览器中工作，但这不是 `vite` 所关心的。\n\n[autoprefixer配置详解](https://segmentfault.com/a/1190000023960072)\n\n\n\n# 185 - c57c3ff chore 单词错误\n\n文档单词修改（没影响，我在184纠正了过来了）。\n\n\n\n# 186 - f80ad1c bump node.js\n\n要求最低的支持版本为`node >= 12.0.0`。\n\n[node 12特性](https://nodejs.medium.com/introducing-node-js-12-76c41a1b3f3f)\n\n> 后续我也会关注一下 为什么要用node 12\n\n\n\n# 187 - 586626f [#46](https://github.com/vitejs/vite/pull/46) 解决没有`<script>`崩溃的问题\n\n添加`\\n`就好了。\n\n```typescript\nconst __script = {}import { updateStyle } from \"/@hmr\"\n\n// 实际上需要的是\nconst __script = {}\nimport { updateStyle } from \"/@hmr\"\n```\n\n\n\n# 188 - e67e0e6 [#47](https://github.com/vitejs/vite/pull/47) 修复test testByPolling 还有`expect`命名冲突\n\n调用`testByPolling`需要添加`await`。`testByPolling`的参数`expect`需要更名，因为这是`js`的关键词。\n\n\n\n# 189 - 4807205 [#48](https://github.com/vitejs/vite/pull/48)chore 修复单词\n\n `recomend` -> `recomended`\n\n\n\n# 190 - ef28ee4 [#49](https://github.com/vitejs/vite/pull/49)构建的时候，关闭`write`后，不应该写入静态资源\n\n原因是缺少`wirte`判断，写漏了。\n\n```typescript\nelse if (emitAssets && write) {\n      // write asset\n      const filepath = path.join(resolvedAssetsPath, chunk.fileName)\n      !silent &&\n        console.log(\n          `write ${chalk.magenta(path.relative(process.cwd(), filepath))}`\n        )\n      await fs.mkdir(path.dirname(filepath), { recursive: true })\n      await fs.writeFile(filepath, chunk.source)\n}\n```\n\n"
  },
  {
    "path": "191-200/191-200.md",
    "content": "# 191 - 72d73b1 [#50](https://github.com/vitejs/vite/pull/46) 修正readme链接\n\n已在[182(4b5d180)](https://github.com/Kingbultsea/vite-analysis/blob/master/181-190/181-190.md#css-pre-processors)修复好。\n\n\n\n# 192 - 7548509 bump vue dep\n\n更新vue(^3.0.0-beta.8)和@vue/compiler-sfc(^3.0.0-beta.8)版本。\n\n\n\n# 193 - b2d4307 v0.10.1\n\nrelease v0.10.1\n\n\n\n# 194 - 354cfad changelog\n\n## [0.10.1](https://github.com/vuejs/vite/compare/v1.0.1...v0.10.1) (2020-05-04)\n\n### Bug Fixes\n\n- 没有script标签会导致程序崩溃 ([#46](https://github.com/vuejs/vite/issues/46)) ([586626f](https://github.com/vuejs/vite/commit/586626fb4099042abe1964700387ee6d0946d43b))\n- `buildOptions.write·设置`false`后不应该再写入assets资源到磁盘中 ([#49](https://github.com/vuejs/vite/issues/49)) ([ef28ee4](https://github.com/vuejs/vite/commit/ef28ee44d690713666d2f9b656276324a0abcd42))\n\n**logmsg**: chore: changelog [ci skip]\n\n> 只要在commit信息中包含`[ci skip]`或 `[skip ci]` ，提交到仓库以后会跳过CI流程\n\n\n\n# 195 - d93fea9 定义自动提交changlog的命令\n\n**logmsg**: workflow: auto push changelog after publishing\n\n> 类似这种流程定义的可以以workflow来作为commit msg的前缀\n\n## package.json\n\n```typescript\n{\n    scripts: {\n        \"postpublish\": \"git add CHANGELOG.md && git commit -m 'chore: changelog [ci skip]' && git push\"\n    }\n}\n```\n\n尤大每次修改了changelog后，用的是命令行，手动输入git命令行，觉得十分麻烦，干脆简化加入package.json脚本。\n\n\n\n# 196 - 1782f83 支持`*.module.css`\n\n## 更新readme css modules描述\n\n### CSS Modules\n\n注意不需要配置PostCSS就可以使用`CSS Modules`: 这是开箱即用的。 在 `*.vue` 组件中你可以使用`<style module>`， 对于纯 `.css` 文件, 你需要命名CSS modules files为 `*.module.css` ， allows you to import the naming hash from it.\n\n## node/serverPluginCss.ts\n\n使用`postcss-modules`包，获取到`json`文件，拼接并返回对象。\n\n```typescript\nlet code =`import { updateStyle } from \"${hmrClientId}\"\\n` +\n          `updateStyle(${id}, ${rawPath})\\n`\n        if (ctx.path.endsWith('.module.css')) {\n          code += `export default ${JSON.stringify(\n            processedCSS.get(ctx.path)!.modules\n          )}`\n        }\n        ctx.body = code.trim()\n```\n\n```typescript\nasync function processCss(ctx: Context) {\n    let css = (await readBody(ctx.body))!\n    let modules\n    const postcssConfig = await loadPostcssConfig(root)\n    const expectsModule = ctx.path.endsWith('.module.css')\n\n    // postcss processing\n    if (postcssConfig || expectsModule) {\n      try {\n        css = (\n          await require('postcss')([\n            ...((postcssConfig && postcssConfig.plugins) || []),\n            ...(expectsModule\n              ? [\n                  require('postcss-modules')({\n                    getJSON(_: string, json: Record<string, string>) {\n                      modules = json\n                    }\n                  })\n                ]\n              : [])\n          ]).process(css, {\n            ...(postcssConfig && postcssConfig.options),\n            from: resolver.requestToFile(ctx.path)\n          })\n        ).css\n      } catch (e) {\n        console.error(`[vite] error applying postcss transforms: `, e)\n      }\n    }\n\n    processedCSS.set(ctx.path, {\n      css,\n      modules\n    })\n  }\n```\n\n### 删除缓存\n\n检测到`*.module.css`的存在，就可以用`processedCSS.delete(publicPath)`删除了。\n\n```typescript\n// handle hmr\n  watcher.on('change', (file) => {\n    if (file.endsWith('.css')) {\n      const publicPath = resolver.fileToRequest(file)\n      const id = hash_sum(publicPath)\n\n      // bust process cache\n      processedCSS.delete(publicPath)\n\n      if (file.endsWith('.module.css')) {\n        watcher.handleJSReload(file)\n      } else {\n        watcher.send({\n          type: 'style-update',\n          id,\n          path: publicPath,\n          timestamp: Date.now()\n        })\n      }\n    }\n  })\n```\n\n## node/builPluginCss.ts\n\n如果遇到`*.module.css`，像server端口那样，返回`export default ${JSON.stringify(modules)}`，改动一致。\n\n\n\n# 197 - dd7af0a fix(moduleResolve)不改写外链import\n\n`dynamic import`是支持外链的。\n\n## serverPluginModuleRewrite.ts\n\n```typescript\n// do not rewrite external imports\nif (/https?:\\/\\//.test(id)) {\n  return\n}\n```\n\n\n\n# 198 - ccce482 修复打包后引用的路径不正确的问题\n\n使用`InjectXXX`方法的时候，需要在路径上添加`assetsDir`。\n\n```typescript\n<link rel=\"stylesheet\" href=\"style.css\">\n<div id=\"app\"></div>\n<script type=\"module\" src=\"index.js\"></script>\n\n<!-- 修复后 -->\n<link rel=\"stylesheet\" href=\"/assets/style.css\">\n<div id=\"app\"></div>\n233\n<script type=\"module\" src=\"/assets/index.js\"></script>\n```\n\n\n\n# 199 - 59c1ab1 文件改名\n\n`node/resolveVue.ts` -> `node/vueResolver.ts`\n\n中文理解，处理vue -> vue处理器。\n\n\n\n# 200 - 5ca0ec4 修复资源路径\n\n## 知识点\n\n```typescript\n\"peerDependencies\": {\n    \"@vue/compiler-sfc\": \"*\"\n }\n```\n\n采用`*`号就不用管编译器的版本更新了(当然开发者得向下兼容得很好)。\n\n## 恢复[transformAssetUrls](https://github.com/Kingbultsea/vite-analysis/blob/4ab007d1bad3afc617dd5bae5edacd815283b3e9/151-160/151-160.md#bug)（vue部分）\n\n```typescript\ntransformAssetUrls: {\n  // @ts-ignore\n  base: path.posix.dirname(publicPath)\n}\n```\n\n设置`base`，相当于修改路径为`path.join(base, '你在template的image资源')`。\n\n想了解的可以看下下面的测试示例哈。\n\n```typescript\n# @vue/compiler-sfc\n\ntest('with explicit base', () => {\n    const { code } = compileWithAssetUrls(\n      `<img src=\"./bar.png\"></img>` + // -> /foo/bar.png\n      `<img src=\"~bar.png\"></img>` + // -> /foo/bar.png\n      `<img src=\"bar.png\"></img>` + // -> bar.png (untouched)\n        `<img src=\"@theme/bar.png\"></img>`, // -> @theme/bar.png (untouched)\n      {\n        base: '/foo'\n      }\n    )\n    expect(code).toMatchSnapshot()\n  })\n// transformAssetUrl\nif (options.base) {\n        // explicit base - directly rewrite the url into absolute url\n        // does not apply to absolute urls or urls that start with `@`\n        // since they are aliases\n        if (\n          attr.value.content[0] !== '@' &&\n          isRelativeUrl(attr.value.content)\n        ) {\n          // when packaged in the browser, path will be using the posix-\n          // only version provided by rollup-plugin-node-builtins.\n          attr.value.content = (path.posix || path).join(\n            options.base,\n            url.path + (url.hash || '')\n          )\n        }\n        return\n      }\n\n\n\ncompileWithSrcset(src, {\n        base: '/foo'\n      })\n<img src='./a.png' setset='/foo/a.png' />\n// transformSrcset\nif (options.base) {\n            const base = options.base\n            const set: string[] = []\n            imageCandidates.forEach(({ url, descriptor }) => {\n              descriptor = descriptor ? ` ${descriptor}` : ``\n              if (isRelativeUrl(url)) {\n                set.push((path.posix || path).join(base, url) + descriptor)\n              } else {\n                set.push(url + descriptor)\n              }\n            })\n            attr.value.content = set.join(', ')\n            return\n          }\n```\n\n## transformAssetUrls（build部分）含BUG\n\n代码修改为：\n\n```typescript\ntransformAssetUrls: {\n          includeAbsolute: true // 是否包括绝对路径\n}\n```\n\n但是实际调试中，如果设置为`true`则是不合理的（可以自己设置一个绝对路径，会出现`Import bug`）。\n\n`options.includeAbsolute`必须为`false`，不然会被处理为`import`语句（或者添加`base`）。\n\n![1](./1.png)\n\n## node/buildPluginCss.ts\n\n如果`url(\"http://123\")`符合`/^https?:\\/\\//`，则不对其做打包处理。\n\n"
  },
  {
    "path": "201-210/201-210.md",
    "content": "# 201 - b48ae5b [#32](https://github.com/vitejs/vite/issues/32) 兼容node 10\n\n![1](./1.png)\n\n尝试修改`\"node\": \">=15.0.0\"`，然后`yarn`:\n\n![2](./2.png)\n\n> 版本号不对，yarn无法安装依赖\n\n\n\n# 202 - 4fa01ca changelog\n\n## [0.10.2](https://github.com/vuejs/vite/compare/v0.10.1...v0.10.2) (2020-05-04)\n\n### Bug Fixes\n\n- 修复构建index资源注入 ([ccce482](https://github.com/vuejs/vite/commit/ccce48228d8220de4312585c716c1c27ea9ef1c2))\n- 正确处理绝对url资源 ([5ca0ec4](https://github.com/vuejs/vite/commit/5ca0ec4abc183a3942ef169b39034ff403dd9eae)), closes [#45](https://github.com/vuejs/vite/issues/45)\n- **moduleResolve:** 不要重写外部导入 ([dd7af0a](https://github.com/vuejs/vite/commit/dd7af0a9b3e77fcbdec6fe7fcda26443f1e2c8fa)), closes [#42](https://github.com/vuejs/vite/issues/42)\n\n### Features\n\n- 支持 *.module.css 的 CSS 模块 ([1782f83](https://github.com/vuejs/vite/commit/1782f831c62e73d961fcf71de4d1024a1f8acaf7))\n\n\n\n# 203 - 95f6ff9 v0.10.2\n\nrelease v0.10.2\n\n\n\n# 204 - b6cafee 支持模板预处理器\n\n```typescript\n// 可以自定义一个预处理器，就算不做任何处理返回原本souce也可以\ndoCompileTemplate({\n        ...options,\n        source: preprocess(options, preprocessor)\n})\n\nfunction preprocess(\n  { source, filename, preprocessOptions }: SFCTemplateCompileOptions,\n  preprocessor: PreProcessor\n): string {\n  // Consolidate exposes a callback based API, but the callback is in fact\n  // called synchronously for most templating engines. In our case, we have to\n  // expose a synchronous API so that it is usable in Jest transforms (which\n  // have to be sync because they are applied via Node.js require hooks)\n  let res: string = ''\n  let err: Error | null = null\n\n  preprocessor.render(\n    source,\n    { filename, ...preprocessOptions },\n    (_err, _res) => {\n      if (_err) err = _err\n      res = _res\n    }\n  )\n\n  if (err) throw err\n  return res\n}\n\n\n# compiler-sfc\nexport type PreprocessLang = string\n\n# node/serverPluginVue.ts\nconst { code, map, errors } = resolveCompiler(root).compileTemplate({\n    // ...\n    preprocessLang: template.lang, // 传入id\n    preprocessCustomRequire: (id: string) => require(resolve(root, id))\n})\n```\n\n\n\n# 205 - a69159a 调整行内式资源最大值为4096\n\n```typescript\nif (!id.endsWith(`.svg`)) {\n    if (content.length < 4096) {\n      url = `data:${mime.lookup(id)};base64,${content.toString('base64')}`\n    }\n}\n```\n\n\n\n# 206 - a182ac4 readme\n\n## Status\n\n仍处于试验阶段，但我们打算使其适合生产。\n\n## Features\n\n...\n\n`vite` 尝试尽可能多地镜像 [vue-cli](http://cli.vuejs.org/) 中的默认配置。如果你之前使用过 `vue-cli` 或其他基于 webpack 的脚手架, 你应该有宾至如归的感觉。\n\n### 与 `vue-cli` 或者其他打包器有什么不同?\n\n主要区别是`vite`在开发模式没有任何代码捆绑。源代码中的 ES 导入语法将直接提供给浏览器, 浏览器通过原生 `<script module>` 支持解析它们, 为每次导入发出 HTTP 请求。开发服务器拦截请求并在必要时执行代码转换。例如, 对 `*.vue` 文件的导入在发送回浏览器之前就会被编译。\n\n这种方法有几个优点：\n\n- 由于没有捆绑工作要做，服务器冷启动速度极快。\n- 代码将会被按需编译, 也就是说只编译当前屏幕上实际导入的代码。您不必等到整个应用程序被捆绑后才能开始开发。这对于具有数十个展示页的应用程序来说可能是一个巨大的差异。\n- 热模块更换 (HMR) 性能与模块总数分离。无论您的应用程序有多大，这都使 HMR 始终保持快速。\n\n整页重新加载可能比基于捆绑程序稍慢, 因为原生 ES 导入会导致具有深度导入链的网络瀑布。但是由于这是本地开发, 与实际编译时间相比，差异应该是微不足道的。 (页面重新加载没有编译成本，因为已经编译的文件会被缓存在内存中。)\n\n最后，因为编译还是在Node中完成，它可以在技术上支持捆绑程序进行任何的代码转换, 并且没有什么能阻碍你最终将代码捆绑到生产环境中。实际上, `vite` 提供了一个 `vite build` 命令来做到这一点，因此应用程序在生产中不会受到网络瀑布的影响。\n\n\n\n# 207 - 4808f41 build支持`ssr`\n\n## `node/build.ts`\n\n### 添加`buildOptions`注释\n\n```typescript\nexport interface BuildOptions {\n  /**\n   * 项目根路径\n   */\n  root?: string\n  /**\n   * 如果为 true，将从 CDN 导入 Vue。\n   * 当存在本地 vue 安装时自动禁用。\n   */\n  cdn?: boolean\n  /**\n   * 映射请求路径/文件路径,\n   * 可选择将模块 ID 映射到公共路径请求。\n   */\n  resolvers?: Resolver[]\n  /**\n   * 默认`dist`\n   */\n  outDir?: string\n  /**\n   * 在`outDir`下的目录下嵌套js / css / static assets\n   * 默认`assets`\n   */\n  assetsDir?: string\n  /**\n   * 构建不在项目根目录内的文件,\n   * 例如，如果你在 vite 之上构建一个更高级别的工具并且包括\n   * 一些将被捆绑到最终构建中的代码。\n   */\n  srcRoots?: string[]\n  /**\n   * 将传递给rollup.rollup()\n   */\n  rollupInputOptions?: InputOptions\n  /**\n   * 将传递给bundle.generate()\n   */\n  rollupOutputOptions?: OutputOptions\n  rollupPluginVueOptions?: Partial<Options>\n  /**\n   * 是否将assets写入磁盘\n   */\n  emitAssets?: boolean\n  /**\n   * 是否将bundle写入磁盘\n   */\n  write?: boolean\n  /**\n   * 是否压缩输出的代码\n   */\n  minify?: boolean\n  /**\n   * 是否将资源信息记录到控制台\n   */\n  silent?: boolean\n}\n```\n\n> 为什么要配置是否写入到磁盘？因为rollup是可以提供用户配置的（而且`vite`构建会返回`output`和`html`），也就是可以配置`output`来做文件输出，所以不需要`vite`的写入，这块主要是考虑给基于`Vite`之上的工具使用，自定义构建。\n\n## `node/build.ts`\n\n新增`ssrbuild`，基于原有方法build传入配置。\n\n#### external：\n\n不进行打包处理的文件，携带`['vue', /^@vue\\//]`。\n\n```typescript\nvar vue = require('vue');\nvar serverRenderer = require('@vue/server-renderer');\n```\n\n### format:\n\n打包后的代码格式，默认`cjs`。\n\n```typescript\nexport type ModuleFormat = InternalModuleFormat | 'commonjs' | 'esm' | 'module' | 'systemjs';\n\n// esm\nimport { createApp } from 'vue';\nimport { foo } from './foo.js';\nimport { ssrRenderAttr } from '@vue/server-renderer';\n\n// cjs\nvar vue = require('vue');\nvar foo_js = require('./foo.js');\nvar serverRenderer = require('@vue/server-renderer');\n```\n\n### exports\n\n导出方式，设置打包后默认`named`。\n\n[详细](https://rollupjs.org/guide/en/#outputexports)\n\n```typescript\nexports?: 'default' | 'named' | 'none' | 'auto';\n```\n\n\n\n# 208 - 288e68e  改进构建输出并包含文件大小信息提示\n\n```typescript\nconst enum WriteType {\n  JS,\n  CSS,\n  ASSET,\n  HTML\n}\n\nconst writeColors = {\n  [WriteType.JS]: chalk.cyan,\n  [WriteType.CSS]: chalk.magenta,\n  [WriteType.ASSET]: chalk.green,\n  [WriteType.HTML]: chalk.blue\n}\n```\n\n\n\n# 209 - a5c608d [#53](https://github.com/vitejs/vite/pull/53)\n\n构建模式下，可以让用户配置内联的阈值，默认4096字符长度。\n\n```typescript\nexport interface AssetsOptions {\n  inlineThreshold?: number\n}\n```\n\n\n\n# 210 - cfdbf4e bump `rollup-plugin-vue`\n\n```json\n{\n    \"rollup-plugin-vue\": \"^6.0.0-alpha.7\"\n}\n```\n\n"
  },
  {
    "path": "21-30/commit-21-30.md",
    "content": "# 21 - 3e0ff79 v0.1.2发布\n\n```json\n{\n-   version: \"0.1.1\"\n+   version: \"0.1.2\"    \n}\n```\n\n\n\n# 22 - c74b24e 重构使用koa，废弃server-handler\n\n均使用koa中间件的形式改写```vueMiddleware.ts moduleMiddleware.ts hmrWatcher.ts```\n\n### server/middlewares/serve.ts\n\n使用```koa2-history-api-fallback```，与```koa-static```中间件。\n\n#### ```koa2-history-api-fallback```:\n\n使用```koa2-connect-history-api-fallback```之后，```koa```就会把所有的get方式的请求都发给```/index.html```,然后由vue-router来接管页面路由。\n\n#### ```koa-static```:\n\n```typescript\napp.use(require('koa-static')(cwd))\n```\n\n获取当前```cwd```路径的静态资源。\n\n### 关于中间件执行的路径处理\n\n1. 检测路径是否为```__hmrClient```，通过他来建立客户端与服务端的```ws```链接。\n2. 处理包含```.js```的路径，发送模块。\n3. 处理包含```.vue```的路径，与前端组件相关。\n4. ```koa2-history-api-fallback```。\n5. ```koa-static```。\n\n\n\n# 23 - a307eeb index.html的指向\n\n去除```koa```的中间件```koa2-history-api-fallback```，采用手写的方法```src/server/middlewares/historyFallback.ts```。\n\n```typescript\nimport { Middleware } from '../index'\n\nexport const historyFallbackMiddleware: Middleware = ({ cwd, app }) => {\n  app.use((ctx, next) => {\n    const cleanUrl = ctx.url.split('?')[0].split('#')[0]\n    if (ctx.method !== 'GET' || cleanUrl.includes('.')) { // 文件 get 不处理\n      return next()\n    }\n\n    if (!ctx.headers || typeof ctx.headers.accept !== 'string') { // 没有header  || 不知道\n      return next()\n    }\n\n    if (ctx.headers.accept.includes('application/json')) { // 不处理 期望json的数据\n      return next()\n    }\n\n    if (\n      !(\n        ctx.headers.accept.includes('text/html') ||\n        ctx.headers.accept.includes('*/*')\n      ) // 边缘处理\n    ) {\n      return next()\n    }\n\n    ctx.url = '/index.html' // 改写路径，交给第5步，处理内容。\n    return next()\n  })\n}\n\n```\n\n因为使用```koa2-history-api-fallback```，会把所有get请求都指向一个文件，如果请求一个```.vue```组件，在进行流程的第```4```步，内容必然会被改写成```index.html```文件。\n\n\n\n# 24 - 4ed433a 改写```index.html```的```<script>```\n\n曾经```index.html```请求页面，页面中的标签，引入```main.js```，经过```moduleRewriter.ts```处理，```import a from 'a'```改写成```import a from '__module/a'```。\n\n现在把这个功能也用在浏览器请求```index.html```中的```<script>```。\n\n优化目的提前一步改写处理。\n\n\n\n# 25 - 36f1a18\n\n### 更改名称:\n\n```vueResole.ts``` -> ```resolveVue.ts``\n\n### 优化改写```import```路径的代码结构\n\n去除```moduleRewriter.ts```，并且把该```rewrite```功能合并到```middlewares/modules.ts```，由于这个用来改写```import```的，所以名称更改为```rewriteImports```。\n\n好处是：原本在vue中间件，模块中间件，都需要指向```__modules```的功能，移交给```modules.ts```统一处理，\n\n通过中间件，好管理代码。\n\n\n\n# 26 - 1c95964 v0.2.0发布\n\n这个版本的```<style>```新增标签后，触发了```rerender```，会导致新增```<style>```无效\n\n```json\n{\n-   version: \"0.1.2\"\n+   version: \"0.2.0\"    \n}\n```\n\n\n\n# 27 - df93fda 准备```vite```配置\n\n### ```build```前需要删除dist: \n\n```json\n{\n    script: {\n        // 不支持windows\n        build: \"rm -rf dist && tsc -p src/client && tsc -p src/server\"\n    }\n}\n```\n\n### ```server/index.ts```\n\n暴露可配置的server，交付给```./bin/vite.js```去建立服务，同时添加```https```选项。\n\n启动```vite```，以命令行的形式去输入配置（这块有BUG，还没完善）\n\n\n\n# 28 - 87324af ```cwd```名称优化\n\n```cwd```参数名称改为```root```，尤大觉得因为寻找模式，直接改为```root```会更加贴切（我觉得尤大觉得）。\n\n\n\n# 29 - 5780a2a 重构```rewriteImports```\n\n### 引入```es-module-lexer```\n\n处理AST语法树，使用在修改```import```所引入的文件。\n\n```typescript\n// 使用新的包，重写rewriteImports\nfunction rewriteImports(source: string) {\n  const [imports] = parse(source)\n\n  if (imports.length) {\n    const s = new MagicString(source)\n    let hasReplaced = false\n    imports.forEach(({ s: start, e: end, d: dynamicIndex }) => {\n      const id = source.substring(start, end)\n      if (dynamicIndex < 0) {\n        if (/^[^\\/\\.]/.test(id)) { // 匹配 / 和 .\n          s.overwrite(start, end, `/__modules/${id}`)\n          hasReplaced = true\n        }\n      } else {\n        // 异步引入import的路径还没有改写\n        // TODO dynamic import\n      }\n    })\n    return hasReplaced ? s.toString() : source\n  }\n\n  return source\n}\n```\n\n\n\n# 30 - 9efbb0e 名称优化\n\n更改参数名称，更改原有的```src/server/middleware``` 为```src/server/plugins```"
  },
  {
    "path": "211-220/211-220.md",
    "content": "# 211 - f1c03ff 设置preserveEntrySignatures\n\n## node/build.ts\n\n`rollup.preserveEntrySignatures = false`\n\n- 类型：`\"strict\" | \"allow-extension\" | false`\n  命令行参数：`--preserveEntrySignatures <strict|allow-extension>`/`--no-preserveEntrySignatures`\n  默认值：`\"strict\"`\n\n  该选项用于控制 Rollup 尝试确保入口块与基础入口模块具有相同的导出。\n\n  - 如果它的值设置为 `\"strict\"`，Rollup 将在入口 chunk 中创建与相应入口模块中完全相同的导出。如果因为需要向 chunk 中添加额外的内部导出而无法这样做，那么 Rollup 将创建一个 `facade` 入口 chunk，它将仅从前其他 chunk 中导出必要的绑定，但不包含任何其他代码。对于库，推荐使用此设置。\n  - 值为 `\"allow-extension\"`，则 Rollup 会将在入口 chunk 中创建入口模块的所有导出，但是如果有必要，还可以添加其他导出，从而避免出现 “facade” 入口 chunk。对于不需要严格签名的库，此设置很有意义。\n  - 值为 `false`，则 Rollup 不会将入口模块中的任何导出内容添加到相应的 chunk 中，甚至不包含相应的代码，除非这些导出内容在 bundle 的其他位置使用。但是，可以将内部导出添加到入口 chunks 中。对于将入口 chunks 放置在脚本标记中的 Web 应用，推荐使用该设置，因为它可能同时减少 bundle 的尺寸大小 和 chunks 的数量。\n\n**Example**\nInput:\n\n```typescript\n// main.js\nimport { shared } from './lib.js';\nexport const value = `value: ${shared}`;\nimport('./dynamic.js');\n\n// lib.js\nexport const shared = 'shared';\n\n// dynamic.js\nimport { shared } from './lib.js';\nconsole.log(shared);\n```\n\nOutput for `preserveEntrySignatures: false`:\n\n```typescript\n// main.js\nimport('./dynamic-39821cef.js');\n\n// dynamic-39821cef.js\nconst shared = 'shared';\n\nconsole.log(shared);\n```\n\n\n\n# 212 - c82a597 打包后的引入路径可配置化\n\n`BuildOptions.base`:\n  类型:：`stirng` | `/`\n  作用：对注入的`css` `js`和静态资源，添加base值路径。\n  **Example**:\n\n```typescript\nbase = 'basePath'\n\n// 转换后的 index.html    \n<link rel=\"stylesheet\" href=\"/base/assets/style.css\">\n<div id=\"app\"></div>\n<script type=\"module\" src=\"/base/assets/index.js\"></script>\n\n// comp.vue\n<template>\n  <img src=\"./assest/pkg.png\"/>\n</template>\n\n// 转换后的 index.js \nvar _imports_0 = \"/base/assets/pkg.61b85fb5.png\";\n```\n\n\n\n# 213 - 59b8638 create-vite-app包 正确设置错误提示的触发\n\n`create-vite-app`包：当文件存在/文件拒绝delete才报错误`console.error(`Error: target directory already exists.`)`\n\n\n\n# 214 - 01135fa [#55](https://github.com/vitejs/vite/issues/55) 修正windows下无法引入模块的问题\n\nvite寻找完模块入口后，会做一次304跳转到模块具体的脚本中。\n\n```typescript\n// 入口跳转\nctx.redirect(path.join(ctx.path, entryPoint))\n```\n\n由于`path.join(ctx.path, entryPoint)`会根据系统不同而返回不同的路径分隔符，在windows下是`\\`，所以使用slash包来做兼容，统一输出为`/`。\n\nwindows： `%5C@modules%5Clodash%5Clodash.js`\nslash后：`/@modules/lodash/lodash.js`\n\n\n\n# 215 - 重构代码，迁移注入html的功能至`buildPluginHtml.ts`\n\n尤大为了一些不知道怎么配置vite的小伙伴（其实就是rollup的配置），加上了配置文档的注释。\n\n```typescript\n  /**\n   * Will be passed to rollup.rollup()\n   * https://rollupjs.org/guide/en/#big-list-of-options\n   */\n  rollupInputOptions?: InputOptions\n  /**\n   * Will be passed to bundle.generate()\n   * https://rollupjs.org/guide/en/#big-list-of-options\n   */\n  rollupOutputOptions?: OutputOptions\n  /**\n   * Will be passed to rollup-plugin-vue\n   * https://github.com/vuejs/rollup-plugin-vue/blob/next/src/index.ts\n   */\n  rollupPluginVueOptions?: Partial<Options>\n```\n\n> Partial<T> 可以快速把某个接口类型中定义的属性变成可选的(Optional)\n\n\n\n# 216 - 8c6cf4a 重构`buildPluginHtml.ts`，处理模板资源路径\n\n改动部分：\n\n- 修改变量名称`BuildOptions.assetsOptions` -> `BuildOptions.assetsInlineLimit`\n- 静态资源添加上`ico`文件类型\n- 新增`buildOptions.emitIndex`，类型可选：`boolean`，是否把`index.html`写入磁盘\n- **重构`buildPluginHtml.ts`**\n\n### 重构`buildPluginHtml.ts`\n\n- 若不存在.html文件，则返回：\n\n```typescript\n{\n      renderIndex: (...args: any[]) => '', // \n      htmlPlugin: null  // rollupPlugin\n}\n\nconst assetAttrsConfig: Record<string, string[]> = {\n  link: ['href'],\n  video: ['src', 'poster'],\n  source: ['src'],\n  img: ['src'],\n  image: ['xlink:href', 'href'],\n  use: ['xlink:href', 'href']\n}\n```\n\n- 编译传入的`.html`\n  1. 收集`<script>`代码，转换`src`属性为`import`语句。\n  2. 符合以`assetAttrsConfig`的`DOM`元素，都会被转换为`import`语句收集起来。\n  3. 返回`html`与收集的`js`。\n\nhtml文件，引入的资源将会使用`buildPuginAsset.resolveAsset`方法替换内部的路径，已确保资源能准确获取。\n\n> 尤大用编译模板`<template/>`的方法`compiler-core.compiler`去处理`.html`文件。\n>\n> ```typescript\n> transform(ast, {\n>   nodeTransforms: [viteHtmlTrasnfrom]\n> })\n> ```\n\n\n\n# 217 - ef2e6ee changelog\n\n## [0.10.2](https://github.com/vuejs/vite/compare/v0.10.1...v0.10.2) (2020-05-04)\n\n### Bug Fixes\n\n- 修复构建时注入`index.html`路径错误问题([ccce482](https://github.com/vuejs/vite/commit/ccce48228d8220de4312585c716c1c27ea9ef1c2))\n- 修复资源路径编译的问题 ([5ca0ec4](https://github.com/vuejs/vite/commit/5ca0ec4abc183a3942ef169b39034ff403dd9eae)), closes [#45](https://github.com/vuejs/vite/issues/45)\n- **moduleResolve:** 不要重写外部导入 ([dd7af0a](https://github.com/vuejs/vite/commit/dd7af0a9b3e77fcbdec6fe7fcda26443f1e2c8fa)), closes [#42](https://github.com/vuejs/vite/issues/42)\n\n### Features\n\n- 通过使用 *.module.css，支持CSS模块功能 ([1782f83](https://github.com/vuejs/vite/commit/1782f831c62e73d961fcf71de4d1024a1f8acaf7))\n\n\n\n# 218 - 09451c6 v0.10.3\n\nrelease v0.10.3\n\n\n\n# 219 - e14644f create-vite-app v1.0.2\n\nrelease create-vite-app v1.0.2\n\n\n\n# 220 - b87ba7e 利用esbuild压缩代码\n\n改动部分：\n\n- package.json新增`esbuild ^0.2.0`包\n- **新增`node/esbuildService.ts`文件**\n- **可选方式压缩代码**`BuildOptions.minify?: boolean | 'terser' | 'esbuild' `\n\n## node/esbuildService.ts\n\n#### `renderChunk`\n\n类型: `(code: string, chunk: ChunkInfo, options: OutputOptions) => string | { code: string, map: SourceMap } | null`\n种类: `async, sequential`\nPrevious Hook: [`resolveFileUrl`](https://rollup.docschina.org/guide/en/#resolvefileurl) \nNext Hook: [`generateBundle`](https://rollup.docschina.org/guide/en/#generatebundle).\n\n可用于转换独立的代码块。被每个 Rollup 所输出的块所调用。返回 `null` 将不应用任何转换。\n\n```typescript\nimport { startService, Service, TransformOptions } from 'esbuild'\nimport { Plugin } from 'rollup'\n\nconst transform = async (\n  service: Service,\n  code: string,\n  options: TransformOptions,\n  operation: string\n) => {\n  console.log(operation)\n  try {\n    const result = await service.transform(code, options)\n    if (result.warnings.length) {\n      console.error(`[vite] warnings while ${operation} with esbuild:`)\n      // TODO pretty print this\n      result.warnings.forEach((w) => console.error(w))\n    }\n    return {\n      code: result.js || '',\n      map: result.jsSourceMap || ''\n    }\n  } catch (e) {\n    console.error(`[vite] error while ${operation} with esbuild:`)\n    console.error(e)\n    return {\n      code: '',\n      map: ''\n    }\n  }\n}\n\nexport const createMinifyPlugin = async (): Promise<Plugin> => {\n  const service = await startService()\n  return {\n    name: 'vite:minify',\n    async renderChunk(code, chunk) { // 如果多个插件实现了相同的钩子函数，那么会串式执行\n      return transform(\n        service,\n        code,\n        { minify: true },\n        `minifying ${chunk.fileName}`\n      )\n    },\n    generateBundle() {\n      service.stop()\n    }\n  }\n}\n\n```\n\n## 可选方式压缩代码\n\n- 旧方式使用`rollup-plugin-terser`\n- 第二种方式使用`esbuild`\n\n"
  },
  {
    "path": "221-230/221-230.md",
    "content": "# 221 - 4fd2cdf 构建模式默认使用esbuild\n\n```typescript\n// terser is used by default for better compression, but the user can also\n// opt-in to use esbuild which is orders of magnitude faster.\nconst minifyPlugin = minify\n  ? minify === 'esbuild'\n    ? await createMinifyPlugin()\n    : require('rollup-plugin-terser').terser()\n  : null\n```\n\n> 现阶段遇到的问题：windows下无法使用esbuild\n\n\n\n# 222 - 7cbaf5d 开发环境编译`jsx`、`tsx`和`ts`\n\n改动部分：\n\n- `node/esbuildService.ts`增加`esbuild`服务是否启动的检测（详）\n- 新增`node/serverPluginEsbuild.ts`（详）\n- `node/serverPluginVue.ts`的`<script lang=\"ts\">`支持转换`ts`（详）\n\n## `esbuildService.ts`增加`esbuild`服务是否启动的检测\n\n新增`ensureService`方法，每次`transform`代码转换前都会检测一次，确保`esbuild`服务运行。\n\n这是由于esbuild服务，有时候会自动退出（主要因为这次改动，`dev`也使用`esbuild`了）。\n\n```typescript\nimport { startService, Service, TransformOptions } from 'esbuild'\n\nconst ensureService = async () => {\n  if (!_service) {\n    _service = await startService()\n  }\n  return _service\n}\n\nexport const transform = async (\n  code: string,\n  options: TransformOptions,\n  operation: string\n) => {\n  return _transform(await ensureService(), code, options, operation)\n}\n```\n\n## 新增`node/serverPluginEsbuild.ts`\n\n使用`esbuild`转换`ts`、`tsx`和`jsx`文件。\n\n```typescript\nimport { Plugin } from './server'\nimport { readBody, isImportRequest, genSourceMapString } from './utils'\nimport { TransformOptions } from 'esbuild'\nimport { transform } from './esbuildService'\n\nconst testRE = /\\.(tsx?|jsx)$/\n\nexport const esbuildPlugin: Plugin = ({ app, watcher, jsxConfig }) => {\n  app.use(async (ctx, next) => {\n    await next()\n    if (isImportRequest(ctx) && ctx.body && testRE.test(ctx.path)) {\n      ctx.type = 'js'\n      let options: TransformOptions = {}\n      if (ctx.path.endsWith('.ts')) {\n        options = { loader: 'ts' }\n      } else if (ctx.path.endsWith('tsx')) {\n        options = { loader: 'tsx', ...jsxConfig }\n      } else if (ctx.path.endsWith('jsx')) {\n        options = { loader: 'jsx', ...jsxConfig }\n      }\n      const src = await readBody(ctx.body)\n      const { code, map } = await transform(\n        src!,\n        options,\n        `transpiling ${ctx.path}`\n      )\n      ctx.body = code\n      if (map) {\n        ctx.body += genSourceMapString(map)\n      }\n    }\n  })\n\n  watcher.on('change', (file) => {\n    if (testRE.test(file)) {\n      watcher.handleJSReload(file)\n    }\n  })\n}\n```\n\n## `node/serverPluginVue.ts`支持转换`ts`\n\n调用`esbuild`服务。\n\n```typescript\nasync function compileSFCMain(\n  descriptor: SFCDescriptor,\n  filePath: string,\n  publicPath: string\n): Promise<string> {\n  // ...\n      \n  let code = ''\n  if (descriptor.script) {\n    let content = descriptor.script.content\n    if (descriptor.script.lang === 'ts') {\n      content = (\n        await transform(content, { loader: 'ts' }, `transpiling ${publicPath}`)\n      ).code\n    }\n\n    code += content.replace(`export default`, 'const __script =')\n  } else {\n    code += `const __script = {}`\n  }\n\n  // ...    \n  return code\n}\n```\n\n## 洋葱模型执行顺序\n\n```typescript\n# node/server.ts\nconst internalPlugins: Plugin[] = [\n  moduleRewritePlugin, // 洋葱模型的第一层（外层） --\n  moduleResolvePlugin, // 洋葱模型的第二层（里层）\n  vuePlugin,           // 洋葱模型的第三层（内层）\n  esbuildPlugin,       // 洋葱模型的第四层（外层） --\n  jsonPlugin,          // 洋葱模型的第五层（外层） --\n  cssPlugin,           // 洋葱模型的第六层（外层） --\n  hmrPlugin,           // 洋葱模型的第七层（里层）\n  serveStaticPlugin    // 洋葱模型的第八层（里层）\n]\n```\n\n\n\n# 223 - 81ffbc5 构建模式支持`ts`\n\n改动部分：\n\n- `node/esbuildService.ts`抽离rollup服务到**新文件**`node/buildPluginEsbuild.ts`\n- `node/serverPluginEsbuild.ts`抽离`ts` `tsx`、`jsx`代码到`node/esbuildService.ts`的`options`（详 **改动二**）\n- `node/esbuildService.ts`判断文件类型使用`loader`（详 **改动三**）\n- 插件上下文和`BuildOptions`新增`jsxConfig`（详 **改动四**）\n\n## 改动二\n\n去除了`ts`的检测，交给`node/esbuildService.ts`判断文件后缀功能处理。\n\n## 改动三\n\n如果没有配置`options.loader`，则判断文件的类型，作为`loader`。\n\n构建的时候利用，自动识别到`ts`而进行转换。\n\n## `node/buildPluginEsbuild.ts`\n\n```typescript\nimport { Plugin } from 'rollup'\nimport { startService, Service } from 'esbuild'\nimport { tjsxRE, transformWithService } from './esbuildService'\n\nexport const createEsbuildPlugin = async (\n  minify: boolean,\n  jsx: {\n    factory?: string\n    fragment?: string\n  }\n): Promise<Plugin> => {\n  let service: Service | undefined\n\n  const jsxConfig = {\n    jsxFactory: jsx.factory,\n    jsxFragment: jsx.fragment\n  }\n\n  return {\n    name: 'vite:esbuild',\n\n    async transform(code, file) {\n      if (tjsxRE.test(file)) {\n        return transformWithService(\n          service || (service = await startService()),\n          code,\n          file,\n          { ...jsxConfig }\n        )\n      }\n    },\n\n    async renderChunk(code, chunk) {\n      if (minify) {\n        return transformWithService(\n          service || (service = await startService()),\n          code,\n          chunk.fileName,\n          {\n            minify: true\n          }\n        )\n      } else {\n        return null\n      }\n    },\n\n    generateBundle() {\n      service && service.stop()\n    }\n  }\n}\n```\n\n## 改动四\n\n[jsxFactory](https://www.typescriptlang.org/tsconfig#jsxFactory)：设置转换的名称\n\n[jsxFragment](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory)：设置Fragment的名称\n\n```typescript\njsxConfig: {\n    jsxFactory: string | undefined\n    jsxFragment: string | undefined\n}\n```\n\n## BUG点\n\n并没有人去处理`<script lang=\"ts\">`。在开发模式中，是交给`esbuild`去处理的。\n\n\n\n# 224 - 8262108 构建模式中对vue文件支持ts\n\n改动部分：\n\n- 默认添加的文件类型：`supportedExts = ['.js', '.jsx', '.ts', '.tsx', '.json']`\n- `util.ts`新增`asyncReplace`，属于代码整理（`serverPluginModuleRewrite.ts` `buildPluginCss.ts`）\n- `node/buildPluginEsbuild.ts`，新增判断`.vue`文件是否含有`lang=\"ts\"`（详 改动三）\n\n## 改动三\n\n**Q**：为什么`rollup-plugin-vue`的`transform`是`async`，`buildPluginEsbuild`是`async`，两者没有任何关联的情况下，能做到转换？\n\n**A**：假如现在有组件`Comp.vue`，执行`rollup-plugin-vue`的`transform`钩子，转换三大块标签为`import`资源，经手`load`钩子返回`script`原内容，然后`buildPluginEsbuild`的`transform`钩子识别到`lang=ts`，进行转换。\n\n```typescript\n// rollup-plugin-vue的transform钩子处理后\nimport script from \"E:\\\\vite\\\\test\\\\temp\\\\Comp.vue?vue&type=script&lang=ts\"\nexport * from \"E:\\\\vite\\\\test\\\\temp\\\\Comp.vue?vue&type=script&lang=ts\"\nimport { render } from \"E:\\\\vite\\\\test\\\\temp\\\\Comp.vue?vue&type=template&id=35b58321\"\nconst cssModules = script.__cssModules = {}\nimport \"E:\\\\vite\\\\test\\\\temp\\\\Comp.vue?vue&type=style&index=0&lang=scss.css\"\nimport style0 from \"E:\\\\vite\\\\test\\\\temp\\\\Comp.vue?vue&type=style&index=0&lang=scss&module=true.css.js\"\ncssModules[\"$style\"] = style0\nscript.render = render\nexport default script\n```\n\n# BUG点\n\n`esbuild`处理后返回了空代码，`ts`是无法使用的\n\n```typescript\n{\n  code: \"\",\n  map: undefinded\n}\n```\n\n\n\n# 225 - 5b5bced \n\n改动部分：\n\n- 脚本配置对象传递修正（详 **改动一**）\n- `esbuild`开发模式中，不检测是否来自`import`请求，也就是`index.html`可以标签引入`ts`了\n- `node/serverPluginModuleRewrite.ts`修改正则的回调参数（详 **修正三**）\n\n## 改动一\n\n通过`--jsx-factory`和`--jsx-fragment`就可以配置对象了。\n\n```typescript\nObject.keys(argv).forEach((key) => {\n  if (argv[key] === 'false') {\n    argv[key] = false\n  }\n  if (key === 'jsx-factory') {\n    ;(argv.jsx || (argv.jsx = {})).factory = argv[key]\n  }\n  if (key === 'jsx-fragment') {\n    ;(argv.jsx || (argv.jsx = {})).fragment = argv[key]\n  }\n})\n```\n\n## 修正三\n\n这是由于正则的表达式获取参数获取错了，导致无法植入`index.html`内的`js`。\n\n```typescript\nctx.body = html!.replace(scriptRE, (_, openTag, script) => { // 之前[_, openTag, script]\n          // also inject __DEV__ flag\n          const devFlag = hasInjectedDevFlag ? `` : devInjectionCode\n          hasInjectedDevFlag = true\n          const ret = `${devFlag}${openTag}${rewriteImports(\n            script,\n            '/index.html',\n            resolver\n          )}</script>`\n          return ret\n})\n```\n\n\n\n# 226 -  73d94b9 修正脚本直接被`index.html`引入后无法触发`hmr`的问题\n\n`hasDeadEnd`，触发`full-reload`。\n\n```typescript\nconst importer = '/index.html'\n\nif (srcAttr) {\n  // register script as a import dep for hmr\n  const importee = cleanUrl(slash(path.resolve('/', srcAttr[1])))\n  debugHmr(`        ${importer} imports ${importee}`)\n  ensureMapEntry(importerMap, importee).add(importer)\n}\n```\n\n> 尤大啥时候修复一下js无法Hmr呢？在windows下\n\n\n\n# 227 - cb205c4 changelog\n\n# [0.11.0](https://github.com/vuejs/vite/compare/v0.10.3...v0.11.0) (2020-05-06)\n\n### Bug Fixes\n\n- 修复index.html直接引入资源无法hmr的问题([73d94b9](https://github.com/vuejs/vite/commit/73d94b9ba75836b995ed276747a32ce94344c1eb))\n\n### Features\n\n- 开发模式支持ts ([7cbaf5d](https://github.com/vuejs/vite/commit/7cbaf5d8e5b70db2ec642bd1d34f1e0322927ccf))\n- 支持使用esbuild压缩代码 ([b87ba7e](https://github.com/vuejs/vite/commit/b87ba7e321b9dd319009a55154540805969f0039))\n- vue组件支持ts ([8262108](https://github.com/vuejs/vite/commit/8262108db14b35126bcaae3253bf3f6391c9d283))\n- 构建模式支持tsx ([81ffbc5](https://github.com/vuejs/vite/commit/81ffbc548c3d5f9db1f040c360167f95963674d6))\n\n\n\n# 228 - 5ba9454 v0.11.0\n\nrelease v0.11.0\n\n\n\n# 229 - ceb7d0a create-vite-app v1.0.3\n\nrelease create-vite-app v1.0.3\n\n\n\n# 230 - 16fa669 readme\n\n### JSX\n\n`.jsx` 和 `.tsx`也同样开箱即用， JSX 也是通过 `esbuild`来编译的, 你可以通过 `--jsx-factory` 和 `--jsx-fragment` 来配置，API可以使用 `jsx: { factory, fragment }` 。例如，在`vite`使用[Preact](https://preactjs.com/) :\n\n```typescript\n{\n  \"scripts\": {\n    \"dev\": \"vite --jsx-factory=h\"\n  }\n}\nimport { h, render } from \"preact\"\nrender(<h1>Hello, what!</h1>, document.getElementById(\"app\"))\n```\n\n#### Notes on JSX Support\n\n- Vue 3的 JSX代码转换尚未完成，所以 `vite`的JSX目前仅支持React/Preact。\n- React不提供ES模块构建，因此必须与Snowpack预先捆绑才能工作。\n- 在使用非Vue框架时，没有现成的HMR，但从技术上讲，HMR是可以通过服务器API实现的。\n\n> 我理解的可以使用custom事件来定义一下（或者直接改源码吧）\n>\n> 2021年12月27日：可以通过hmr api来实现，后续vue hmr将重构为使用hmr api\n\n"
  },
  {
    "path": "231-240/231-240.md",
    "content": "# 231 - e78c9f7 修复外链import路径改写错误\n\n不应该中断改写`import`流程，不然遇到外链后，不返回任何的`code`。\n\n```typescript\nif (isExternalUrl(id)) {\n  break\n}\n```\n\n\n\n# 232 - redame\n\n### JSX\n\n`.jsx` 和 `.tsx`也同样开箱即用， JSX 也是通过 `esbuild`来编译的。\n\n因为 React 不提供 ES 模块构建，你可以使用 [es-react](https://github.com/lukejacksonn/es-react), 或者使用 Snowpack 将 React 预捆绑到 ES 模块中。 让它运行的最简单方法是:\n\n```js\nimport { React, ReactDOM } from 'https://unpkg.com/es-react'\n\nReactDOM.render(<h1>Hello, what!</h1>, document.getElementById(\"app\"));\n```\n\n\n\n# 233 - fcf709e changelog \n\n## [0.11.1](https://github.com/vuejs/vite/compare/v0.11.0...v0.11.1) (2020-05-06)\n\n### Bug Fixes\n\n- 修复外链import路径改写错误 ([e78c9f7](https://github.com/vuejs/vite/commit/e78c9f7680c2652b13f4270182c860417e388b2e))\n\n\n\n# 234 - a135bfd v0.11.1\n\nrelease v0.11.1\n\n\n\n# 235 - 2048b65 [#59](https://github.com/vitejs/vite/pull/59) readme修改\n\n**Message**：我注意到HMR的代码在构建后没有被删除，这将会导致错误，因为`hot`被编译成一个空对象了。\n我不知道你是否可以删除这部分的代码, 虽然使用 `__DEV__`也可以正常运行。\n\n即使用HMR API都需要使用`__DEV__`判断：\n\n```typescript\nif (__DEV__) {\n  hot.accept(newModule => {\n    console.log('updated: count is now ', newModule.count)\n  })\n}\n\nif (__DEV__) {\n  hot.accept('./foo.js', (newFoo) => {\n    // the callback receives the updated './foo.js' module\n    newFoo.foo()\n  })\n\n  // Can also accept an array of dep modules:\n  hot.accept(['./foo.js', './bar.js'], ([newFooModule, newBarModule]) => {\n    // the callback receives the updated mdoules in an Array\n  })\n}\n```\n\n\n\n# 236 - 5ca417e 定义如何提交BUG/功能报告的模板\n\n配置好即可在`issues` -> `new issues`  查看到。\n\n![docs](./docs.png)\n\n![docs2](./docs2.png)\n\n## .github/ISSUE_TEMPLATE/bug_report.md\n\nname: 标题\n\nabout: 描述\n\nlabels: 标签\n\ntitle: 内容标题\n\n```markdown\n---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n## Describe the bug\n\nA clear and concise description of what the bug is.\n\n## System Info\n\n- `vite` version\n- OS (required):\n- Node version (required):\n- Optional:\n  - Installed `vue` version (from `yarn.lock` or `package-lock.json`)\n  - Installed `@vue/compiler-sfc` version\n\n## Logs\n\n1.  **run `vite` or `vite build` with the `DEBUG` environment variable set to `vite:*`** - e.g. modify the `dev` script in your `package.json` to:\n\n    ``` bash\n    DEBUG=vite:* vite\n    ```\n\n    On windows, you will need [cross-env](https://www.npmjs.com/package/cross-env):\n\n    ``` bash\n    cross-env DEBUG=vite:* vite\n    ```\n\n2. Provide the error log here.\n\n## Reproduction\n\nProvide a link to a reproduction repo if applicable.\n\n```\n\n## .github/ISSUE_TEMPLATE/feature_request.md\n\n```markdown\n---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n\n```\n\n> 我之前以为debug包失效了... 后来去查看了一下文档[debug](https://www.npmjs.com/package/debug)使用方式，发现需要一个`process.env.DEBUG`来开启。\n\n\n\n# 237 - 更新bug报告模板\n\n加粗提示`vite`版本 操作系统 node版本的需求。\n\n\n\n# 238 - 353bd81 文档英文单词语法修改 [#64](https://github.com/vitejs/vite/pull/64)\n\n文档英文单词语法修改。\n\n\n\n# 239 - 906b14c readme todo\n\n## TODOs\n\n- 支持构建配置 (custom import maps and plugins)\n- 通过[Sucrase](https://github.com/alangpierce/sucrase) or [esbuild](https://github.com/evanw/esbuild)支持 TypeScript / Flow /(P)React JSX\n\n> 话说不是已经支持了？除了Flow\n\n\n\n# 240 - bd58858 避免改变 esbuild 选项\n\n相当于代码整理，让`options.sourcemap`可配置，再也不是`true`来固定。\n\n"
  },
  {
    "path": "241-250/241-250.md",
    "content": "# 241 - 5f05f1e 简化`web_modules`路径处理器\n\n改动：\n\n- `node/serverPluginModuleResolve.ts`，`resolveWebModule`被重构（详 **改动一**）\n\n## 改动一\n\n```typescript\nasync function resolveWebModule(\n  root: string,\n  id: string\n): Promise<string | undefined> {\n  let webModulePath = webModulesMap.get(id)\n  if (webModulePath) {\n    return webModulePath\n  }\n  webModulePath = path.join(root, 'web_modules', id + '.js')\n  if (await fs.pathExists(webModulePath)) {\n    webModulesMap.set(id, webModulePath)\n    return webModulePath\n  }\n}\n```\n\n## 什么是`webModules`?\n\n使用方式：\n\n创建文件夹`web_modules`，在文件夹下创建`webmodules.js`，使用`import 'webmodules'`引入。\n\n```typescript\n// # Comp.vue\nimport 'webmodules'\n```\n\n所以从功能和代码上看，作用仅仅是路径转换。\n\n\n\n# 242 - e01e26d `url('data:')`应跳过处理\n\n改动：\n\n`node/buildPluginCss.ts`，转换`url(./foo.png)`为资源的处理器，应跳过对`url(data:)` `inlineData`的处理。\n\n\n\n# 243 - fc75323 修复`web_modules`在构建中无法使用的问题\n\n改动部分：\n\n- `node/buildPluginResolve.ts`，引入`node/serverPluginModuleResolve.ts`中的`resolveWebModule`方法。（详 **改动一**）\n\n## 改动一\n\n修改`resolveId`为异步方式，因不依赖其他插件`resolveId`的处理，所以可以异步。\n\n如果检测到路径非`@`且非`/`，则执行`resolveWebModule`，没有寻找到`web_module`即空，不做任何处理。\n\n也就是说检测`import a from 'myLodash'`，尝试寻找`web_module/myLodash.js`，有则转换为`import a from 'web_module/myLodash.js'`。\n\n> 顺带一提，rollup已经帮忙处理好node_modules了，我们不用管这一层东西\n\n```typescript\nasync resolveId(id: string) {\n      if (id === hmrClientId) {\n        return hmrClientId\n      } else if (id.startsWith('/')) {\n        // if id starts with any of the src root directories, it's a file request\n        if (srcRoots.some((root) => id.startsWith(root))) {\n          return\n        }\n        const resolved = resolver.requestToFile(id)\n        debug(id, `-->`, resolved)\n        return resolved\n      } else if (id === 'vue') {\n        if (!cdn) {\n          return resolveVue(root).bundler\n        } else {\n          return {\n            id: resolveVue(root).cdnLink,\n            external: true\n          }\n        }\n      } else if (!id.startsWith('.')) {\n        const request = resolver.idToRequest(id)\n        if (request) {\n          const resolved = resolver.requestToFile(request)\n          debug(id, `-->`, request, `--> `, resolved)\n          return resolved\n        } else {\n          const webModulePath = await resolveWebModule(root, id)\n          if (webModulePath) {\n            return webModulePath\n          }\n        }\n      }\n    }\n```\n\n\n\n# 244 - 7aaf458 兼容[#59](https://github.com/vitejs/vite/pull/59)，定义构建字符串的范围\n\n改动部分：\n\n- `node/build.ts`对`@rollup/plugin-replace`(替换字符串)插件配置范围为`['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx']`。\n- `node/buildPluginResolve.ts`对返回的`hot`，增加`accept` `on`，主要是为了调用的时候为空的问题（[#59](https://github.com/vitejs/vite/pull/59)）。\n\n\n\n# 245 - 6490a8d 添加`playground`测试项目\n\n用于检测`vite`的BUG。\n\n包含：\n\n- `web_modules`\n\n- `@hmr`使用\n\n- 模块处理\n\n- `hmr`\n\n- `.css`引入\n\n- `scope css`\n\n- `.module.css`\n\n- `template`预处理器`pug`\n\n- 行内式资源，`url()`外链资源\n\n- `JSON`引入\n\n- `JSX` `TSX`应用\n\n- `async`组件\n\n  \n\n# 246 - d02d694 完善功能测试\n\n改动部分：\n\n- 去除test/fixtures/的模板文件，test大改（详 **改动一**）\n- 新增`node/buildPluginReplace.ts`，替换`@rollup/plugin-replace`（详 **改动二**）\n\n## 改动一\n\n检测的事情：\n\n- 通过检测DOM字符串，判断是否正确渲染\n- 通过检测`consolelog`输出是否包含404，判断资源是否正常生成\n- 检测`__DEV__`，`false`为构建环境，`true`为开发环境\n- `process.env.NODE_ENV`替换\n- 检测`vue-router` `vuex`，判断`node_modules`模块是否正常\n- 检测`web-modules-dep`，判断`web_modules`是否正常\n- 替换文件`<template>`内容，检测`vue-render`\n- 替换文件`<script>`，检测`vue-reload`\n- 检测`js`是否被正常引入\n- 检测`js`更新，冒泡触发`vue-reload`\n- 检测`js`更新，`hot api`\n- `posocss`与`hmr`\n- `scope`与`hmr`\n- `css module`与`hmr`\n- `css` `import`与`hmr`\n- `<template>`预处理器`pug`\n- `json`与`hmr`\n- `jsx`与`hmr`\n\n### dev如何配置测试\n\n`execa`执行命令，页面跳转到相应`url`。\n\n```typescript\ndevServer = execa(binPath, ['--jsx-factory=h'], {\n  cwd: tempDir\n})\n\npage = await browser.newPage()\npage.on('console', (msg) => logs.push(msg.text()))\nawait page.goto('http://localhost:3000')\n```\n\n### build如何配置测试\n\n`execa`执行命令，使用`koa` `http` `koa-static`运行服务。\n\n```typescript\nconst buildOutput = await execa(binPath, ['build', '--jsx-factory=h'], {\n        cwd: tempDir\n})\n\nconst app = new (require('koa'))()\napp.use(require('koa-static')(path.join(tempDir, 'dist')))\nstaticServer = require('http').createServer(app.callback())\nawait new Promise((r) => staticServer.listen(3001, r))\n\npage = await browser.newPage()\nawait page.goto('http://localhost:3001')\n```\n\n### 其他点\n\n`jest --runInBand`： 在当前进程中连续运行所有测试， 而不是创建运行测试的子进程 作为工作池，这样可以大幅度提高运行速度。\n\n[`getComputedStyle`](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/getComputedStyle)：返回一个对象，该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。 私有的CSS属性值可以通过对象提供的API或通过简单地使用CSS属性名称进行索引来访问。\n\n## 改动二\n\n`rollup/plugin-replace`会执行两次替换，第一次在`transform`，第二次在`renderChunk`（转换`chunk`）。\n\n而且`vue组件`会被转换为`js`文件，我们无法把`vue`文件给排除在外(`js`等于`vue`组件)。\n\n```typescript\n# rollup/plugin-replace\nreturn {\n    name: 'replace',\n\n    renderChunk: function renderChunk(code, chunk) {\n      var id = chunk.fileName;\n      if (!keys.length) { return null; }\n      if (!filter(id)) { return null; }\n      return executeReplacement(code, id);\n    },\n\n    transform: function transform(code, id) {\n      if (!keys.length) { return null; }\n      if (!filter(id)) { return null; }\n      return executeReplacement(code, id);\n    }\n  };\n\n# rollup/plugin-replace.ts\nimport { Plugin, TransformResult } from 'rollup'\nimport MagicString from 'magic-string'\n\nconst filter = /\\.(j|t)sx?$/\n\nexport const createReplacePlugin = (\n  replacements: Record<string, string>\n): Plugin => {\n  const pattern = new RegExp(\n    '\\\\b(' +\n      Object.keys(replacements)\n        .map((str) => {\n          return str.replace(/[-[\\]/{}()*+?.\\\\^$|]/g, '\\\\$&')\n        })\n        .join('|') +\n      ')\\\\b',\n    'g'\n  )\n\n  return {\n    name: 'vite:replace',\n    transform(code, id) {\n      if (filter.test(id)) {\n        const s = new MagicString(code)\n        let hasReplaced = false\n        let match\n\n        while ((match = pattern.exec(code))) {\n          hasReplaced = true\n          const start = match.index\n          const end = start + match[0].length\n          const replacement = replacements[match[1]]\n          s.overwrite(start, end, replacement)\n        }\n\n        if (!hasReplaced) {\n          return null\n        }\n\n        const result: TransformResult = { code: s.toString() }\n        // TODO source map\n        // result.map = s.generateMap({ hires: true })\n        return result\n      }\n    }\n  }\n}\n```\n\n\n\n# 247 - `vite.js`改写为`cli.ts`\n\n原文件使用`require`引入，不用担心`bin`位置的问题。\n\n## `node/cli.ts`\n\n```typescript\nimport chalk from 'chalk'\nimport { RollupError } from 'rollup'\nimport { networkInterfaces } from 'os'\n\nconsole.log(chalk.cyan(`vite v${require('../package.json').version}`))\nconst s = Date.now()\nconst argv = require('minimist')(process.argv.slice(2))\n\nif (argv.help) {\n  // TODO print supported args on --help\n}\n\nObject.keys(argv).forEach((key) => {\n  // cast xxx=false into actual `false`\n  if (argv[key] === 'false') {\n    argv[key] = false\n  }\n  // map jsx args\n  if (key === 'jsx-factory') {\n    ;(argv.jsx || (argv.jsx = {})).factory = argv[key]\n  }\n  if (key === 'jsx-fragment') {\n    ;(argv.jsx || (argv.jsx = {})).fragment = argv[key]\n  }\n})\n\nif (argv._[0] === 'build') {\n  console.log('Building for production...')\n  require('../dist')\n    .build({\n      ...argv,\n      cdn: argv.cdn === 'false' ? false : argv.cdn\n    })\n    .catch((err: RollupError) => {\n      console.error(chalk.red(`[vite] Build errored out.`))\n      // TODO pretty print this\n      // rollup errors contain helpful information\n      console.error(err)\n      process.exit(1)\n    })\n} else {\n  const server = require('../dist').createServer(argv)\n\n  let port = argv.port || 3000\n\n  server.on('error', (e: Error & { code?: string }) => {\n    if (e.code === 'EADDRINUSE') {\n      console.log(`Port ${port} is in use, trying another one...`)\n      setTimeout(() => {\n        server.close()\n        server.listen(++port)\n      }, 100)\n    } else {\n      console.error(chalk.red(`[vite] server error:`))\n      console.error(e)\n    }\n  })\n\n  server.on('listening', () => {\n    console.log(`Dev server running at:`)\n    getIPv4AddressList().forEach((ip) => {\n      console.log(`  > http://${ip}:${port}`)\n    })\n    console.log()\n    require('debug')('vite:server')(`server ready in ${Date.now() - s}ms.`)\n  })\n\n  server.listen(port)\n}\n\nfunction getIPv4AddressList() {\n  const interfaces = networkInterfaces()\n  let result: string[] = []\n\n  Object.keys(interfaces).forEach((key) => {\n    const ips = (interfaces[key] || [])\n      .filter((details) => details.family === 'IPv4')\n      .map((detail) => detail.address.replace('127.0.0.1', 'localhost'))\n\n    result = result.concat(ips)\n  })\n\n  return result\n}\n```\n\n\n\n# 248 - 72e021b 重构整理文件位置\n\n改动部分：\n\n- 新增`open: ^7.0.3` 未使用(详 **`open`**)\n- 调整文件位置，根据功能划分于不同的文件夹\n\n## open\n\n[`open-npm`](https://www.npmjs.com/package/open) 打开诸如 URL、文件、可执行文件之类的东西。跨平台。在`vite`用于快速打开浏览器。\n\n\n\n# 249 - 957945f 支持配置`--open`打开浏览器\n\n改动部分：\n\n- `node/cli.ts`，新增判断open参数，true则利用`require('./utils/openBrowser').OpenBrowser(http://${addresses[0]}:${port})`打开浏览器\n- 新增`bin/openChrome.applescript`、`src/node/utils/openBrowser.ts` (搬运 [` create-react-app`](https://github.com/facebook/create-react-app/blob/bb64e31a81eb12d688c14713dce812143688750a/packages/react-dev-utils/openBrowser.js))\n\n\n\n# 250 - 29d0bcd changelog\n\n## [0.11.2](https://github.com/vuejs/vite/compare/v0.11.1...v0.11.2) (2020-05-07)\n\n### Bug Fixes\n\n- 防止`esbuild options`冲突 ([bd58858](https://github.com/vuejs/vite/commit/bd588584231cd41fb016811cf22f76d0ffa13c72))\n- 修复构建时对`web_modules`的处理 ([fc75323](https://github.com/vuejs/vite/commit/fc75323ff5861318a77c0680eb94a094ceee0b27))\n- 构建模式下，对`css`的`data uri`不应该做资源转换处理 ([e01e26d](https://github.com/vuejs/vite/commit/e01e26dc93070b995d75784bb48e97a024148338)), closes [#66](https://github.com/vuejs/vite/issues/66)\n- `hot.accept`没有裹上`if (__DEV__)`将会被警告 ([7aaf458](https://github.com/vuejs/vite/commit/7aaf45816fe5ceadb163b5faa294eebf26044c62))\n\n### Features\n\n- 支持`--open`标志 ([957945f](https://github.com/vuejs/vite/commit/957945faada703513174151a4fff4cf2f97f6efc))\n\n"
  },
  {
    "path": "251-260/251-260.md",
    "content": "# 251 - db52dd3 v0.11.2\n\nrelease v0.11.2\n\n> 我在某版本号规范上看到，如果改动了代码功能，都应该第二位增加，第三位是文档类的修改。但是现在似乎让我感觉不对，因为我们每次修改发布`npm`如果是文档，不需要修改小版本号提交上去。\n> 功能多了或者修复的多了就第二位增加比较对。\n\n\n\n# 252 - c51f335 readme\n\n由于之前整理了文件的位置，所以文档内引用了文件的`url`需要修改。\n\n\n\n# 253 - baa020e bump `esbuild` version\n\n`esbuild: \"^0.2.0\"` -> `esbuild: \"^0.2.4\"`\n\n> lock也更新了，所以是有必要的\n\n\n\n# 254 - ca421cd fix [#61](https://github.com/vuejs/vite/issues/61) 无法拓展类型\n\n他遇到的问题是引入不了`ts`类型的文件，大概是因为路径出错找不到文件类型(`resolver.requestToFile`)，windows下并没有遇到这个问题。\n\n> windows下`esbuild`也无法使用，需要升级`esbuild v0.2.6`。\n>\n> 修改方法使用`path.posix`，`js`的`hmr`问题会得到解决。\n\n\n\n# 255 - c7a5a69 bump `esbuild`\n\n`esbuild`升级，`windows`下可以转换`ts`了\n\n\n\n# 256 - b1d6be7 完善错误提示\n\n改动部分：\n\n- 新增`ora ^4.4.4`，优雅的“转圈圈”\n- 增加`esbuild`代码位置的错误提示\n\n![1](./1.png)\n\n\n\n# 257 - 38b8ada changelog\n\n## [0.11.3](https://github.com/vuejs/vite/compare/v0.11.2...v0.11.3) (2020-05-07)\n\n### Bug Fixes\n\n- 修复模块重写结果 (fix [#61](https://github.com/vuejs/vite/issues/61)) ([ca421cd](https://github.com/vuejs/vite/commit/ca421cdf9348076a53ad1ff1a9e6ee4095776eae))\n\n### Features\n\n- 改进构建错误输出 ([b1d6be7](https://github.com/vuejs/vite/commit/b1d6be7cf3e436fce7b187d2139ee43349ca5f40))\n\n\n\n# 258 - 3a81be4 release v0.11.3\n\nrelease v0.11.3\n\n\n\n# 259 - 6b63b34 replace构建支持输出`soucemap`\n\n### 开发环境下一个`Vue组件`被分成3块，那`sourcemap`如何粘合起来的？\n\n第一块为`js`输出组件，第二块`render`，第三块`css`。\n\n```typescript\nupdateStyle(\"92a6df80-0\", \"/Comp.vue?type=style&index=0\") // css\n__script.__scopeId = \"data-v-92a6df80\"\nimport { render as __render } from \"/Comp.vue?type=template\" // render\n__script.render = __render\n__script.__hmrId = \"/Comp.vue\"\n__script.__file = \"E:\\\\vite\\\\test\\\\temp\\\\Comp.vue\"\nexport default __script // 自身\n```\n\n我们可以拦截`genSourceMapString`方法，输出`sourcemap`，分别创建两个文件作为比较，`my`为**第一块**`js`，`template`为第二块`render`：\n\n![2](./2.png)\n\n使用`reverse-sourcemap`转换：\n\n```shell\nreverse-sourcemap -o my -v my.js.map\n```\n\n```sh\nreverse-sourcemap -o template -v template.js.map\n```\n\n查看两者路径比对：\n\n![my](./3.png)\n\n![template](./4.png)\n\n发现文件转换后的内容一致，并没有任何不同(`...`为省略，`sourcemap`是不一样的，不转换查看`sourcesContent`也会发现一样，不一样的是对应位置)：\n\n```typescript\n<template>\n...\n</template>\n\n<script lang=\"ts\">\n...\n</script>\n\n<style scoped lang=\"scss\">\n...\n</style>\n\n```\n\n通过打印发现，`template`和`script`的`map`转换内容是一样的，把目标缩短到`const descriptor = await parseSFC(root, filePath, ctx.body)` -> `parseSFC` ->`resolveCompiler`  -> `'@vue/compiler-sfc'包` -> `parse`。\n\n`block.content`为`<script>` `<template>`内容。\n\n```typescript\n#compiler-sfc/parse.ts 关键代码\n\nif (sourceMap) {\n    const genMap = (block: SFCBlock | null) => {\n      if (block && !block.src) {\n        block.map = generateSourceMap(\n          filename,\n          source,\n          block.content,\n          sourceRoot,\n          !pad || block.type === 'template' ? block.loc.start.line - 1 : 0\n        )\n      }\n    }\n    genMap(descriptor.template)\n    genMap(descriptor.script)\n    descriptor.styles.forEach(genMap)\n  }\n\nfunction generateSourceMap(\n  filename: string,\n  source: string,\n  generated: string,\n  sourceRoot: string,\n  lineOffset: number\n): RawSourceMap {\n  const map = new SourceMapGenerator({\n    file: filename.replace(/\\\\/g, '/'),\n    sourceRoot: sourceRoot.replace(/\\\\/g, '/')\n  })\n  map.setSourceContent(filename, source)\n  generated.split(splitRE).forEach((line, index) => {\n    if (!emptyRE.test(line)) {\n      const originalLine = index + 1 + lineOffset\n      const generatedLine = index + 1\n      for (let i = 0; i < line.length; i++) {\n        if (!/\\s/.test(line[i])) {\n          map.addMapping({\n            source: filename,\n            original: {\n              line: originalLine,\n              column: i\n            },\n            generated: {\n              line: generatedLine,\n              column: i\n            }\n          })\n        }\n      }\n    }\n  })\n  return JSON.parse(map.toString())\n}\n```\n\n#### `generated.split`做了什么？\n\n组件的起始行偏移值，我们可以把组件第一行空1行，发现`lineOffset`为`1`。\n\n把`js`转换成`ast`，`ast`内记录了行数，利用`addMapping`方法对应行的映射。\n\n> `render`需要调整映射一次，比`js`的要复杂，而`js`仅仅需要偏移`lineOffset`，想了解可以看`compiler-sfc`的`mapLines`\n\n我们还试试`template`报错，会出现什么情况吧，因为会转换为render，我们触发`undefinded`即可。\n\n```html\n<div class=\"a\" v-if=\"foo.foo.foo.foo.foo\" />\n```\n\n![5](./5.png)\n\n点击`E:\\vite\\test\\temp\\Comp.vue:3`:\n\n![6](./6.png)\n\n结论：**`sourcesContent`是一样的，但是映射是不一样的，所以不是所谓的粘合**。\n\n[source-map](https://www.npmjs.com/package/source-map#sourcemapgenerator)\n\n### 那构建的`sourcemap`为什么只有一个文件，不需要合并吗？\n\n我们再使用`reverse-sourcemap`转换我们的文件。\n\n![7](./7.png)\n\n发现连文件夹对应的位置都还原了，100%还原效果（没用的代码`treeshaking`掉了）。\n\n查阅`rollup-plugin-vue`，能发现`<template> <style> <script>`都有自己的`sourcemap`。\n\n`sourcemap`的合并也是很简单的，两个合并起来也相当于代码转换，把偏移的位置给加上即可。\n\n所以说使用rollup转换代码，只要确保返回自己的`sourcemap`即可，rollup自动会帮你合并的，这也是为什么`vue`默认生成`sourcemap`，控制rollup是否开启`sourcemap`操作即可。\n\n [source-map](https://stackoverflow.com/questions/19330344/how-to-read-base64-vlq-code)\n\n> 注意，mappings的VLQ的值都是**相对值**，不管多少个分号，值都要从第一个分号累加起来。\n>\n> 我们使用`magic-string`的`generateMap({ hires: true })`生成各自的map就可以了，rollup帮我们自动合并处理（没有则返回null）。\n\n\n\n# 260 - 19f8358 在test中删除`spinner`\n\n因为test环境下`process.env.NODE_ENV === ‘test’`，所以可以根据这删除转圈圈。\n\n"
  },
  {
    "path": "261-270/261-270.md",
    "content": "# 261 - 44a8250 增强错误输出\n\n改动部分：\n\n- `server/serverPluginVue.ts`，错误详细位置输出。\n\n![1](1.png)\n\n```typescript\nif (errors.length) {\n    console.error(chalk.red(`\\n[vite] SFC template compilation error: `))\n    errors.forEach((e) => {\n      if (typeof e === 'string') {\n        console.error(e)\n      } else {\n        console.error(\n          chalk.underline(\n            `${filename}:${e.loc!.start.line}:${e.loc!.start.column}` // 文件位置 包括列行\n          )\n        )\n        console.error(chalk.yellow(e.message)) // 报错提示信息\n        const original = template.map!.sourcesContent![0]\n        console.error(\n          generateCodeFrame(original, e.loc!.start.offset, e.loc!.end.offset) // 输出视图\n        )\n      }\n    })\n  }\n```\n\n## 能够准确找到错误位置，原理是什么？\n\n往上一步步寻找`require.resolve('@vue/compiler-sfc').compileTemplate`编译会返回这个`errors`。\n\n![2](2.png)\n\n我们逆向寻找，在触发的地方打上断点：\n\n![3](3.png)\n\n寻找调用栈相关代码：\n\n![4](4.png)\n\n`traverseNode`转换AST树为代码，遇到props需要调用`buildProps`进行参数分析，发现转换的是指令相关代码，获取相对应的转换器`transformModel`。\n\n![6](6.png)\n\n**触发原因为当前AST树节点非`input` `textarea` `select`标签**。\n\n![7](7.png)\n\n传递当前`loc`：\n\n![8](8.png)\n\n返回`error`:\n\n![9](9.png)\n\n`vite`检测到`errors`：\n\n![10](10.png)\n\n调用本次commit改动的方法，输出到控制台中。\n\n#### `generateCodeFrame`做的事情\n\n通过当前`template`的`sourcemap`获取到源码，与`error`的`loc`偏移量一同传递给`generateCodeFrame`：\n\n![11](11.png)\n\n换行符分割计算出行数，遍历行，通过字符累加count，如果  count >=start，即可以判断目标为当前行，`range`被固定为`2`，即输出目标行上下各三行。\n\n`' '.repeat(3 - String(line).length)`: 为了保持排版，行数为双数需要去除掉一个空格，也就是说保持百行的代码排版。\n\n![12](12.png)\n\n```typescript\nfunction generateCodeFrame(source, start = 0, end = source.length) {\n    const lines = source.split(/\\r?\\n/); // 行\n    let count = 0;\n    const res = [];\n    for (let i = 0; i < lines.length; i++) {\n        count += lines[i].length + 1;\n        if (count >= start) {\n            for (let j = i - range; j <= i + range || end > count; j++) {\n                if (j < 0 || j >= lines.length)\n                    continue;\n                const line = j + 1;\n                res.push(`${line}${' '.repeat(3 - String(line).length)}|  ${lines[j]}`);\n                const lineLength = lines[j].length;\n                if (j === i) { // 如果等于当前行，需要在loc长度的代码加上'^'标记\n                    // push underline\n                    const pad = start - (count - lineLength) + 1;\n                    const length = Math.max(1, end > count ? lineLength - pad : end - start);\n                    res.push(`   |  ` + ' '.repeat(pad) + '^'.repeat(length));\n                }\n                else if (j > i) { // 如果长度覆盖了不止一行\n                    if (end > count) {\n                        const length = Math.max(Math.min(end - count, lineLength), 1);\n                        res.push(`   |  ` + '^'.repeat(length));\n                    }\n                    count += lineLength + 1;\n                }\n            }\n            break;\n        }\n    }\n    return res.join('\\n');\n}\n```\n\n> 分析template的AST，转换AST为代码过程中检测是否错误，错误返回AST的位置，通过`sourcemap`获取到源码，通过AST错误位置切割源码，打印源码错误位置与信息到控制台\n\n\n\n# 262 - 7bfba6c ci 删除`esbuild .install文件`兼容`esbuild`的bug \n\n![13](13.png)\n\n![14](14.png)\n\n[`esbuild`为什么快？ ](https://juejin.cn/post/6992448851990806558)\n\n`esbuild`如何运行的：`child_process`运行`esbuild.exe`，通过`promise`，`esbuild.exe`结束后，`child_process`触发`close`，`close`回调执行`resolve`返回数据。\n\n[`postinstall`是什么](https://docs.npmjs.com/cli/v7/using-npm/scripts#examples): 在执行`npm install`也会自行`postinstall`脚本。\n\n阅读`esbuild` `install.js`，作用为：\n\n- 根据`process.platform`判断平台，当前为`windows`，执行`installOnWindows`，下载`esbuild-windows-64@0.2.6`，改名为`esbuild.exe`\n- 把启动`esbuild.exe`的`js`代码写进`bin/esbuild`（为什么要这样做... 直接bin不行吗？除非是写`esbuild`的时候多个平台用到代码，为了聚合起来，连`install`文件都是自动生成的）\n\n![15](15.png)\n\n**手动删除原因：**\n\n```typescript\n// esbuild 0.2.12 修复了该BUG\n// 事实证明，一些包管理器 (例如 yarn) \n// 在我们已经安装之后，有时重新运行这个包的 postinstall 脚本。\n// 这意味着这个脚本必须是幂等（指的是同样的方法被执行一次与连续执行多次的效果是一样的）的。 \n// 如果发生了这种情况，我们将跳过这次包的安装\nif (fs.existsSync(installDir)) {\n    return false;\n}\n```\n\n> 查看了`vite`仓库目前最新的代码，已经删除了该改动\n\n\n\n# 263 - df77197 重构简化`esbuild`服务使用\n\n改动部分：\n\n- `node/cli.ts`: 构建完成后调用`process.exit(0)`（目前没有遇到影响，不知道如何触发）\n- `node/esbuildService`: 简化代码，新增`  stopService()`（详 **改动二**）\n- `node/build/buildPluginEsbuild.ts`: 简化代码，去除`generateBundle`，不使用`stop()`\n- `node/build/index.ts`: rollup构建完成后，调用`stopService()`\n\n## 改动二\n\nstop方法为`esbuild`包内方法，即`child_process`的`stop`停止子进程：\n\n```typescript\n // This stops the service, which kills the long-lived child process. Any\n  // pending requests will be aborted.\n  stop(): void;\n  \n  stop() {\n        child.kill();\n      }\n```\n\n\n\n# 264 - 4df840d bump deps\n\n![16](16.png)\n\n\n\n# 265 - ffd1fee 组件支持`src` 引入文件\n\n改动部分：\n\n`node/server/serverPluginCss.ts`:  不处理遇到来自组件`src`的请求，去除`.modules.css`的`hmr`(迁移)\n\n`node/server/serverPluginEsbuild.ts`: 去除`jsx tsx`的`hmr`(迁移)\n\n`node/server/serverPluginHmr.ts`:  非`.css`文件，但是`.module.css`的文件可以触发`hmr`\n\n`node/server/serverPluginJson.ts`: 去除`json` `hmr`（迁移）\n\n`node/server/serverPluginVue.ts`: 新增`resolveSrcImport`方法，新增资源引入的`hmr`，如`style`的`src`触发`vue-style-update`事件（详 改动五）\n\n> 现在有测试用例，比较好看出尤大想解决什么问题\n\n### 改动五\n\n除了设置`koa-ctx、block.content`文件内容和返回文件路径外，其调用`ensureMapEntry`和`serverPluginModulesRewrite.ts`的收集关系链调用的方法是一样的，建立文件关系，为了`hmr`能够准确寻找该触发的更新事件和目标。\n\n```typescript\nasync function resolveSrcImport(\n  block: SFCBlock,\n  ctx: Context,\n  resolver: InternalResolver\n) {\n  const importer = ctx.path\n  const importee = slash(path.resolve(path.dirname(importer), block.src!))\n  const filename = resolver.requestToFile(importee)\n  await cachedRead(ctx, filename)\n  block.content = ctx.body\n\n  // register HMR import relationship\n  debugHmr(`        ${importer} imports ${importee}`)\n  ensureMapEntry(importerMap, importee).add(ctx.path)\n  return filename\n}\n```\n\n#### 为什么普通文件都直接触发`handleJSReload`?\n\n可以去查看[洋葱模型](https://github.com/Kingbultsea/vite-analysis/blob/master/221-230/221-230.md#%E6%B4%8B%E8%91%B1%E6%A8%A1%E5%9E%8B%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F)，最后执行的是`node/serverPluginModuleRewrite.ts`，注册import importee关系map，只要注册了，就可以根据普通文件路径触发`hmr`。\n\n#### `vue`组件到底怎么处理`src`的呢？\n\n注册import importee关系，`AST`描述树通过读取文件资源内容，手动设置`content`字段。\n\n所以可以和标签里的内容保持一致性的处理。\n\n\n\n# 266 - f3265c1 `create-vite-app` 使用规范的`html`作为模板 [#76](https://github.com/vitejs/vite/pull/76)\n\n使用 `create-vite-app` 创建应用程序时，生成的 index.html 并不真正有效。我在现有内容周围添加了基本的 html5 内容。\n\n\n\n# 267 - 9f6f0a6 修复windows下，路径改写不正确的问题 [#73](https://github.com/vitejs/vite/pull/76)\n\n使用了`path.posix`的方法兼容好了。\n\n> 但是有没有注意到265的改动五中的importee，一样没修复，导致`src`的`hmr`失效，后续尤大应该会整理代码，合并两者的。\n\n\n\n# 268 - 82414b8 整理266的代码，合并两者功能\n\n改动部分：\n\n- `node/utils/pathUtils.ts`: 新增`resolveRelativeRequest`方法（详 **改动一**）\n- `node/server/serverPluginModuleRewrite.ts`：去除路径处理，使用`resolveRelativeRequest`，代码迁移\n- `node/server/serverPluginVue.ts`：去除路径处理，使用`resolveRelativeRequest`，也是代码迁移\n\n### 改动一\n\n把两个文件的功能整合了在一起。\n\n```typescript\nimport path from 'path'\nimport slash from 'slash'\n\nexport const queryRE = /\\?.*$/\nexport const hashRE = /\\#.*$/\n\nexport const cleanUrl = (url: string) =>\n  url.replace(hashRE, '').replace(queryRE, '')\n\nexport const resolveRelativeRequest = (importer: string, id: string) => {\n  const resolved = slash(path.posix.resolve(path.dirname(importer), id))\n  const queryMatch = id.match(queryRE)\n  return {\n    url: resolved,\n    pathname: cleanUrl(resolved),\n    query: queryMatch ? queryMatch[0] : ''\n  }\n}\n```\n\n> query在`serverPluginModuleRewrite.ts`的作用为恢复原本路径的参数，另一个功能为如果改写前后路径相同就不继续调用改写了。\n\n\n\n# 269 - 2053c8a 修复windows报错后无法退出结束测试的问题\n\n![17](17.png)\n\n> 需要把`package.json`的`build`，`rm -rf`给删除了，才能跑测试。\n>\n> 不过还是跑失败，提示文件夹资源被占用，无法`rmdir`去除，尤大肯定注意到了这个问题，我猜的是他目前先快速解决，后续用ci配置windows的测试来发现问题。\n\n\n\n# 270 - 946b978 ci 添加`appveyor.yml`进行windows下的自动化测试\n\n[Appveyor](https://www.appveyor.com/docs/)\n\n```typescript\nenvironment:\n  nodejs_version: \"12\" // node版本\n\ninstall:\n  - ps: Install-Product node $env:nodejs_version // 使用PowerShell安装node版本\n  - yarn // 安装yarn\n\ntest_script:\n  - git --version\n  - node --version\n  - yarn --version\n  - yarn test\n\ncache:\n  - node_modules -> yarn.lock // yarn.lock变动node_modules的缓存也变动\n\nbuild: off \n// 关闭MSBuild模式，MSBuild模式会导致AppVeyor寻找Visual Studio项目，并使用它去进行构建\n// MSBuild 模式的替代方案是脚本模式。\n// 此模式允许你通过运行任意脚本化操作而不是构建 Visual Studio 项目来进行构建。\n\n```\n\n使用过程，进入[Appveyor](https://ci.appveyor.com/projects/new)，点击`Install AppVeyor App`按钮：\n\n![18](18.png)\n\n选择项目：\n\n![19](19.png)\n\n点击 项目右边的 `add`：\n\n![20](20.png)\n\n顺带`esbuild`的`install`脚本还发现了安装`esbuild win32`有问题。\n\n"
  },
  {
    "path": "271-280/271-280.md",
    "content": "# 271 - 0c3c546 ci windows使用node x64架构\n\n因为`esbuild`不支持在node x86架构中使用，所以切换为x64架构。\n\n[node架构](https://newsn.net/say/node-arch.html)：返回为其编译 Node.js 二进制文件的操作系统 CPU 架构。可能的值为 `'arm'`, `'arm64'`, `'ia32'`, `'mips'`, `'mipsel'`, `'ppc'`, `'ppc64'`, `'s390'`, `'s390x'`, `'x32'`, 和 `'x64'`.\n\nia32即x86。\n\n```typescript\n// esbuild install.js\n// Pick a package to install\nif (process.platform === 'linux' && os.arch() === 'x64') {\n  installOnUnix('esbuild-linux-64');\n} else if (process.platform === 'darwin' && os.arch() === 'x64') {\n  installOnUnix('esbuild-darwin-64');\n} else if (process.platform === 'win32' && os.arch() === 'x64') {\n  installOnWindows();\n} else {\n  console.error(`error: Unsupported platform: ${process.platform} ${os.arch()}`);\n  process.exit(1);\n}\n```\n\n![1](1.png)\n\n\n\n![2](2.png)\n\n[本次ci运行信息](https://ci.appveyor.com/project/Kingbultsea/sbuild/builds/40544835)\n\n> 64位状态下兼容32位软件，但是有些驱动什么专属的是不兼容的。\n\n\n\n# 272 - 0345c36 拆分脚本，为windows ci添加测试`--forceExit`标记\n\n![3](3.png)\n\n上次`windows ci`运行测试错误后，`jest`并没有停止。\n\n`--forceExit`: 强制Jest在所有测试运行完后退出。 对于一些由测试所生成但无法充分清理的资源来说，这是很有用的。\n\n\n\n# 273 - 4f685b3 typo 272脚本拼写错误\n\n`buiild` -> `build`\n\n\n\n# 274 - 5741b79 [#71](https://github.com/vitejs/vite/issues/71)\n\ndev与`build`统一使用`vue.runtime.esm-bundler`，为了一些依赖`vue`其他包的第三方库能在同一个地方统一获取。\n\n\n\n# 275 - 904266b [#74](https://github.com/vitejs/vite/issues/74) 支持目录index解析\n\n有一些包，会使用`import xxx from 'xxx'`的形式引入脚本，需要支持这种方式。\n\n![4](4.png)\n\n\n\n改动部分：\n\n- `node/resolver.ts`: 方法更名，`ensureExt` ->  `resolveExt` (详 **改动一**)\n- `node/serverPluginModuleRewrite.ts` :  `/util/` -> `/util` (详 **改动二**)\n\n### 改动一\n\n支持三种寻找方式。\n\n- `foo/` -> `foo/index.js`\n- `foo` -> `foo.js`\n- `foo` -> `foo/index.js`\n\n会引起`windows``BUG`？因为不能直接使用`/index`，需要根据系统修改路径分割号。（不会直接引起BUG，因为都是当作`publicPath`使用了，只是不规范，留坑）\n\n![5](5.png)\n\n```typescript\nexport const supportedExts = ['.js', '.ts', '.jsx', '.tsx', '.json']\n\nconst resolveExt = (id: string) => {\n  const cleanId = cleanUrl(id)\n  if (!/\\.\\w+$/.test(cleanId)) {\n    const expectsIndex = id[id.length - 1] === '/'\n    let inferredExt = ''\n    for (const ext of supportedExts) {\n      if (expectsIndex) {\n        try {\n          // foo/ -> foo/index.js\n          statSync(id + 'index' + ext)\n          inferredExt = 'index' + ext\n          break\n        } catch (e) {}\n      } else {\n        try {\n          // foo -> foo.js\n          statSync(id + ext)\n          inferredExt = ext\n          break\n        } catch (e) {\n          try {\n            // foo -> foo/index.js\n            statSync(id + '/index' + ext)\n            inferredExt = '/index' + ext\n            break\n          } catch (e) {}\n        }\n      }\n    }\n    const queryMatch = id.match(/\\?.*$/)\n    const query = queryMatch ? queryMatch[0] : ''\n    return cleanId + inferredExt + query\n  }\n  return id\n}\n```\n\n### 改动二\n\n改写`import`路径，如果发现其使用了`resolveExt`自动寻找到`index`，则删除路径的尾符号`/`，方便合并。\n\n也就是支持`import '/util/'` -> `import '/util/index.js'`\n\n\n\n# 276 - 7f952c7 重构 简化ext解析逻辑\n\n不需要`foo/` -> `foo/index.js`，因为在`node/serverPluginModuleRewrite.ts` 改写`import`处理了\n\n```typescript\ntry {\n        // foo -> foo.js\n        statSync(id + ext)\n        inferredExt = ext\n        break\n      } catch (e) {\n        try {\n          // foo -> foo/index.js\n          statSync(path.join(id, '/index' + ext))\n          inferredExt = '/index' + ext\n          break\n        } catch (e) {}\n      }\n```\n\n\n\n\n\n# 277 - 0be5c31 changelog\n\n## [0.11.4](https://github.com/vuejs/vite/compare/v0.11.3...v0.11.4) (2020-05-07)\n\n### Bug Fixes\n\n- tests 去除 spinner ([19f8358](https://github.com/vuejs/vite/commit/19f8358a47251b35557f4c2bdd8a3ac2b7ef96c0))\n- 修复windows路径解析 ([#73](https://github.com/vuejs/vite/issues/73)) ([9f6f0a6](https://github.com/vuejs/vite/commit/9f6f0a619af6f7fba22033b9540680862df3dc09))\n- 修复windows路径解析 ([82414b8](https://github.com/vuejs/vite/commit/82414b88bb057630f096123fb820105817c4707c)), 关闭[#69](https://github.com/vuejs/vite/issues/69) [#72](https://github.com/vuejs/vite/issues/72)\n-  支持目录index解析 (关闭[#74](https://github.com/vuejs/vite/issues/74)) ([904266b](https://github.com/vuejs/vite/commit/904266bc726e672926da3b01a8990dccd16d4e8b))\n- vue使用esm-bundler build([5741b79](https://github.com/vuejs/vite/commit/5741b798c1dc535d83154e5c0e9f1c3e7e5f92b7)), 关闭[#71](https://github.com/vuejs/vite/issues/71)\n\n\n\n# 278 - bddca2e v0.11.4\n\nrelease v0.11.4\n\n\n\n# 279 - eb0a885 [#56](https://github.com/vitejs/vite/issues/56) 支持`monorepos` `hmr`且修复`menorepos`无法使用`VUE SFC`组件的问题\n\n[`MonoRepo`是什么](https://segmentfault.com/a/1190000038683978)\n\n`workspace`包之间共享`node_modules`：[链接](https://www.jianshu.com/p/990afa30b6fe)\n\n用户使用`yarn link`，引入包内的`SFC`组件，此时被插件改写为`@modules/XXX/XX.vue`，发送到浏览器，请求`@modules/XXX/XX.vue`，经过`serverPluginModuleResolve.ts`处理后没有调用下一个插件，固然vue没有被编译就会被发送到浏览器。\n\n改动部分：\n\n- `node/resolver.ts`: `publicPath`与`fiePath`互转过程中，如果发现路径曾在`serverPluginModuleResolve.ts`中被处理过，则返回被处理过的路径。\n- `serverPluginModuleResolve.ts`使用`serve`方法后，需要调用next进行下一个组件，且对文件添加`hmr`。(详 **改动二**)\n\n### 改动二\n\n识别到`@modules`，获取具体文件路径，302跳转，再次识别到`@modules`，调用`serve`，返回文件内容。\n\n现在返回到文件内容后，需要经过`serverPluginVue.ts`处理，所以调用`next`。\n\n软链的路径是这里寻找到的（实际就指向父级，`workspace-a/Comp.vue`不会报错， `packages/workspace-a/Comp.vue`会报错，`vite`并没做任何处理）：\n\n![6](6.png)\n\n如果像`yarn link packages/workspace-a`套了多一个路径：\n\n![7](7.png)\n\n> 当前没有测试用例，可以自行通过yarn link来进行测试，修复点为`next()`的调用。\n\n\n\n# 280 - 08b6259 changelog\n\n## [0.11.5](https://github.com/vuejs/vite/compare/v0.11.4...v0.11.5) (2020-05-07)\n\n### Bug Fixes\n\n- 支持monorepos (close [#56](https://github.com/vuejs/vite/issues/56)) ([eb0a885](https://github.com/vuejs/vite/commit/eb0a88514df344cbe4be3165cfa1a26af4f9f6ef))\n\n"
  },
  {
    "path": "281-290/281-290.md",
    "content": "# 281 - c2806d7 v0.11.5\n\nrelease v0.11.5\n\n\n\n# 282 - 29099ae 修复在不进行本地安装的情况下处理vue问题\n\n改动部分：\n\n- `node/build/buildPluginResolve.ts`，去除没必要的srcRoots资源目录；`@vue/`资源将统一从`@vue/runtime-dom/dist/**.esm-bundler.js`中获取，即与vue引入的包统一。\n- `node/build/index.ts`，去除调用`node/build/buildPluginResolve.ts createBuildResolvePlugin`的`srcRoots`参数传入，符合改动。\n- `node/server/serverPluginModuleResolve.ts`，`@vue/`资源将统一从`@vue/runtime-dom/dist/**.esm-bundler.js`中获取。\n\n在[`commit-5741b79`](https://github.com/vitejs/vite/commit/5741b798c1dc535d83154e5c0e9f1c3e7e5f92b7)中，指向的是`vue/dist/vue.runtime.esm-bundler.js`，现在调整为`@vue/runtime-dom/dist/**.esm-bundler.js`，不需要通过`vue`作为中介去获取其包内方法达到统一。\n\n\n\n# 283 - 4c5a31e 修复index解析双附加\n\n改动部分：\n\n- `node/server/serverPluginModulesRewrite.ts`，拓展文件后缀，遇到`lodash/index`应该删除`index`再进行拓展，即`lodash` + `/index.js`；对于自身`import`自身的情况，不再做`importer` `importee`映射处理。\n- `node/resolver.ts resolveExt`，拓展后缀需要使用clearnId删除`url hash`（但实际上`url hash`并不会传递给服务器的，浏览器有规定，提交过PR，但后来发现rollup构建会传入`hash`，集成的测试是需要的）\n\n\n\n# 284 - cd8794c 如果是src导入，则仅销毁非vue文件上的vue缓存\n\n`node/server/serverPluginHmr.ts `，对于非`vue`文件的hmr，需要删除`VUE SFC`缓存。\n\n并不清楚该改动实际作用：\n\n`.vue`通过`src`引入的`.js`，调用的删除不正确。\n\n![1](1.png)\n\n> 外部引入资源，只会触发vue-reload。\n\n\n\n# 285 - 3ba0104 changelog\n\n# [0.12.0](https://github.com/vuejs/vite/compare/v0.11.5...v0.12.0) (2020-05-07)\n\n### Bug Fixes\n\n- 修复index解析双附加 ([4c5a31e](https://github.com/vuejs/vite/commit/4c5a31e7b32e63ffb219cf75d8c69ce482a5753d))\n- 修复在不进行本地安装的情况下处理vue问题 ([29099ae](https://github.com/vuejs/vite/commit/29099ae214d9ad8d8bfe3b930a509087450f3e38))\n- 如果是src导入，则仅销毁非`vue`文件上的`vue`缓存 ([cd8794c](https://github.com/vuejs/vite/commit/cd8794c380559aae45908a64708214b2d0778c93))\n\n\n\n# 286 - v0.12.0 bdec134\n\nrelease v0.12.0\n\n\n\n# 287 - a4694ba create-vite-app v1.0.4 去除`yarn.lock`\n\n去除`yarn.lock`\n\n\n\n# 288 - c2e5044 readme\n\n文档中所有`vite`改为Vite。\n\n\n\n# 289 - 8e1e09c create-vite-app 添加debug命令 [#84](https://github.com/vitejs/vite/pull/84)\n\n为了调试起来更方便。![2](2.png)\n\n\n\n# 290 - e1dd37f readme\n\n### 和 [Snowpack](https://www.snowpack.dev/) 有什么不一样?\n\n// ...\n\n这就是说，因为Vite支持解析`web_modules`，所以您可以在Vite项目中使用Snowpack预绑定依赖项（这可以减少开发期间的网络请求），以加快整个页面的重新加载。\n"
  },
  {
    "path": "291-300/291-300.md",
    "content": "# 291 - 9061e44 支持使用`js`引入静态资源 + 打包`public`资源特殊处理\n\n改动部分：\n\n- `node/build/buildPluginAsset.ts resolveAsset`:  `public`开头的路径，将被打包进以`publicBase`参数的值为文件夹(详 **改动一**)\n- `node/build/buildPluginCss.ts`：调用`resolveAsset`没有检测到资源，则不做任何处理，以免空资源被注入`bundle`中(`registerAssets`)\n- `node/build/index.ts`：如果`/public`存在，则复制其资源（详 **改动三**）\n- 新增`node/server/serverPluginAssets.ts`，静态资源转换为`export default \"路径\"`（详 **新增四**）\n\n### 改动一\n\n之前的资源统一打包的路径为：`slash(path.join(publicBase, assetsDir, resolvedFileName))`，即现在遇到`/^public(\\/|\\\\)/`，去除`assetsDir`。\n\n```typescript\n// 获取相对路径\nconst pathFromRoot = path.relative(root, id)\n\n// 检测是否public开头的文件路径\n  if (/^public(\\/|\\\\)/.test(pathFromRoot)) {\n    // assets inside the public directory will be copied over verbatim\n    // so all we need to do is just append the baseDir\n    resolved = {\n      content: null,\n      fileName: null,\n      // 是则资源会被放到publicBase传入的参数\n      url: slash(path.join(publicBase, pathFromRoot))\n    }\n  }\n```\n\n### 改动三\n\n`import icon from './public/icon.png'` -> **改动一**识别到public，则不把其路径改为`assets`，意义就是不要将`public`的资源打包进`assets`中。\n\n```typescript\n// vite 写入\nif (write) {\n    //skpi...\n    \n    // /public的资源都会被打包到/public中，全部均为复制\n    const publicDir = path.resolve(root, 'public')\n    if (await fs.pathExists(publicDir)) {\n      await fs.copy(publicDir, path.resolve(outDir, 'public'))\n    }\n}\n```\n\n![1](1.png)\n\n### 新增四\n\n转换为`js`模块语言。\n\n```typescript\nimport { Plugin } from '.'\nimport { isImportRequest, isStaticAsset } from '../utils'\n\nexport const assetPathPlugin: Plugin = ({ app }) => {\n  app.use(async (ctx, next) => {\n    if (isStaticAsset(ctx.path) && isImportRequest(ctx)) {\n      ctx.type = 'js'\n      ctx.body = `export default ${JSON.stringify(ctx.path)}`\n      return\n    }\n    return next()\n  })\n}\n\n```\n\n> `import { Plugin } from '.'`，自动加载`/index.ts`\n\n\n\n# 292 - 12a5d47 支持 --debug 标志 for windows\n\n改动部分\n\n`node/cli.ts`： 参数中传入`debug`，将设置`process.env.DEBUG = true`。\n\n> 本次commit，添加了`crocess-env`，为了windows能够正确设置环境变量(程序变量？)\n\n\n\n# 293 - 30c9bea 调整292\n\n需要添加上`vite:`，否则不能触发（DEBUG包，已经有解析），如`require('debug')('vite:build:asset')`。\n\n![2](2.png)\n\n\n\n# 294 - e5cf447 `hmr`功能，支持`hot.dispose`\n\n改动部分\n\n- readme 因为新增了`hot.dispose`用例，需要给大家说一下dispose的使用（详 **改动一**）\n- `node/server/serverPluginHmr.ts`\n- `client/client.ts`，先进行`disposer()`，再发送请求获取新的js文件，获取完成后调用accept的回调方法，这样dispose触发就可以获取旧变量了，新的js文件将按照原来流程被调用callback\n- `node/server/serverPluginHmr.ts`，AST语法树识别`dispose`（详 **改动四**）\n\n### 改动一\n\n使用`hot.dispose`，`callback`调用栈为改动前的值。\n\n```js\nfunction setupSideEffect() {}\nfunction cleanupSideEffect() {}\n\nsetupSideEffect()\n\nif (__DEV__) {\n  hot.dispose(cleanupSideEffect)\n}\n```\n\n测试用例(`testHmrManual.js`)：\n\n```typescript\nimport { hot } from '@hmr'\n\nexport const foo = 1\n\nif (__DEV__) {\n  hot.accept(({ foo }) => {\n    console.log('foo is now: ', foo)\n  })\n\n  hot.dispose(() => {\n    console.log('foo was: ', foo)\n  })\n}\n\n// jest\n      test('hmr (manual API)', async () => {\n        await updateFile('testHmrManual.js', (content) =>\n          content.replace('foo = 1', 'foo = 2')\n        )\n        await expectByPolling(() => logs[logs.length - 1], 'foo is now:  2')\n        // there will be a \"js module reloaded\" message in between because\n        // disposers are called before the new module is loaded.\n        expect(logs[logs.length - 3]).toMatch('foo was:  1')\n      })\n```\n\n### 改动四\n\n根据语法树识别到`callee`名称为`dispose`，即在对应位置插入当前调用该`hot.dispose api`的`.js`文件名称。\n\n![3](3.png)\n\n```typescript\nif (node.callee.property.name === 'dispose') {\n    // inject the imports's own path to dispose calls as well\n    s.appendLeft(node.arguments[0].start!, JSON.stringify(importer) + ', ')\n}\n```\n\n\n\n# 295 - 5d3cc75 重构ci，封装代码\n\n改动部分:\n\n- `node/cli.ts`: 完全重构了，整理了一下代码结构（详 **改动一**）\n\n### 改动一\n\n1. 封装启动serve的服务为`runServe`，`build`构建为`runBuild`\n2. 参数处理统一封装为`parseArgs`\n\n\n\n# 296 - a882aa4 cli新增 --help 标志\n\n```typescript\nfunction logHelp() {\n  console.log(`\nUsage: vite [command] [args] [--options]\n\nCommands:\n  vite                       Start server in current directory.\n  vite serve [root=cwd]      Start server in target directory.\n  vite build [root=cwd]      Build target directory.\n\nOptions:\n  --help, -h                 [boolean] 显示帮助\n  --version, -v              [boolean] 显示版本\n  --port                     [number]  服务端口\n  --open                     [boolean] 自动打开浏览器\n  --base                     [string]  构建public位置 (default: /)\n  --outDir                   [string]  构建输出位置 (default: dist)\n  --assetsDir                [string]  在dist文件下，设置输出资源位置 (默认: assets)\n  --assetsInlineLimit        [number]  最大行内式资源大小bytes(默认: 4096 byte)\n  --sourcemap                [boolean] 构建生成sourcemap (默认: false)\n  --minify                   [boolean | 'terser' | 'esbuild'] 设定压缩服务(默认: 'terser')\n  --jsx-factory              [string]  (默认: React.createElement)\n  --jsx-fragment             [string]  (默认: React.Fragment)\n`)\n}\n```\n\n#### `vite`直接把`public`的复制到`dist/public`，是一个写死的代码，如果我设置了`--base '/asd'`是不是会报错？\n\n雀氏。\n\n![4](4.png)\n\n\n\n# 297 - 5111d42 调整`BuildOptions`的参数位置\n\n也许更好看。（但是对于我来说，改了和没改一样）\n\n不如把参数也跟着类型的位置调整？更美观了。\n\n![5](5.png)\n\n\n\n# 298 - 3af44fc readme 添加关于TS模块隔离的注释\n\n### TypeScript\n\n从v0.11开始， `Vite`支持 在`*.vue` 中设置`<script lang=\"ts\">` , 也可以引入 `.ts` 文件。 请注意，`Vite`**不**执行类型检查 - 类型检查由IDE和构建过程负责 (你可以在在构建脚本中执行 `tsc --noEmit`). 考虑到这一点, `vite`使用[`esbuild`](https://github.com/evanw/esbuild)将TypeScript转换为JavaScript，速度大约是原生`js` 写的`tsc`的20~30倍，HMR更新可以在不到50毫秒的时间内反映在浏览器中。\n\n> `esbuild`帮你构建`ts`了，所以不会做类型检测(`esbuild`不做这个，对于类型会替换为空格，[劝劝`evanw`?](https://github.com/evanw/esbuild/issues/95))\n\n#### 执行`tsc --noEmit`可以检测`.ts`，也可以检测`.vue`麽？[#749](https://github.com/vitejs/vite/issues/749)\n\n很多人说，自己做一个类型检测并不难，提取`<script>`内容为`.ts`就可以检测了。\n\n尤大说，有一个[工具](https://github.com/vuedx/languagetools)可以做到，然后过了三个月尤大添加上了这个功能了。\n\n![6](6.png)\n\n\n\n# 299 - eab49a4 hmr API更换路径为`vite/hmr` + 添加`hmr`类型\n\n改动部分：\n\n- `package.json`:  `files`包括`hmr.d.ts`（files即上传到`npm`的文件），jest添加 `--clearCache`（详 **改动一**）\n- `hmrClientId`更改为`vite/hmr`，即`@hmr`相关的路径，都需要更改为`vite/hmr`。\n\n### 改动一\n\n### `--cache`\n\n是否使用缓存。默认为true。使用 `--no-cache` 禁用缓存。注意：只有在遇到与缓存有关的问题时，才应禁用缓存。平均而言，禁用缓存会使Jest至少慢两倍。\n\n如果要检查缓存，请使用 `--showConfig` 并查看 `cacheDirectory` 值。如果需要清除缓存，请使用 `--clearCache` 。\n\n转换脚本已更改或Babel已更新，Jest无法识别这些更改？\n\n尝试使用 [`--no-cache`](https://jestjs.io/zh-Hans/docs/cli#--cache) 选项。 Jest 会缓存转换的模块文件来加速测试的执行。如果你正在使用自己的自定义转换器，考虑添加`getCacheKey` 方法 [getCacheKey in Relay](https://github.com/facebook/relay/blob/58cf36c73769690f0bbf90562707eadb062b029d/scripts/jest/preprocessor.js#L56-L61)。\n\n> 所以尤大遇到缓存问题了。（这就不再深入探究了，问他本人吧... 蛤蛤蛤 我windows系统也不好测）\n\n\n\n# 300 - 393fc52 构建返回的hot需要带`dispose`\n\n之前也有提到过，免得报错，所以加一个空白调用。\n\n![7](7.png)\n\n"
  },
  {
    "path": "301-310/301-310.md",
    "content": "# 301 - 253da59 changelog\n\n### [0.13.0](https://github.com/vuejs/vite/compare/v0.12.0...v0.13.0) (2020-05-08)\n\n#### Features\n\n- **`hmr`:** `hmr`路径更换为 `vite/hmr` + 添加类型 ([eab49a4](https://github.com/vuejs/vite/commit/eab49a4b7dd7e3bb0ff215c7e7937814cd63bb4f)), 关闭[#92](https://github.com/vuejs/vite/issues/92)\n- cli添加帮助信息 ([a882aa4](https://github.com/vuejs/vite/commit/a882aa48cb447ec3b84019a2ce838ee75d848555))\n- **`hmr`:** 支持`hot.dispose` ([e5cf447](https://github.com/vuejs/vite/commit/e5cf447762c73aafd686a69a8b0d8e24c4e00048))\n- cli支持 --debug 标志 ([12a5d47](https://github.com/vuejs/vite/commit/12a5d47b2bf2cb7e1badae2e2ee1129c0ae29fe5))\n- 支持`js`引入资源 + 对 `/public`文件夹的特殊处理 ([9061e44](https://github.com/vuejs/vite/commit/9061e442a7de8f94ca2931299450464f78f82148))\n\n\n\n# 302 - 04d5561 v0.13.0\n\nrelease v0.13.0\n\n\n\n# 303 - 04d5561 补充301的`changlog`\n\n因为`vite/hmr`的更改，使用`vite` `hmr`的人都需要更改，这是一项破坏性的改动。\n\n![1](1.png)\n\n\n\n# 304 - d85e751 `create-vite-app` v1.0.5\n\nrelease `create-vite-app` v1.0.5\n\n\n\n# 305 - e2185b4 [#90](https://github.com/vitejs/vite/pull/90)文档语法错误\n\n英文老师来了\n\n\n\n# 306 - 3653793 `web_modules`后缀处理 + 处理`Import`语句的错误提示\n\n改动部分：\n\n- `node/server/serverPluginModuleResolve.ts`： 处理`web_modules`时，如果请求id非`.js`后缀，则自动添加`.js`后缀。\n- `node/server/serverPluginModulerEWRITE.ts`：利用`es-module-lexer`包中的`parse`处理`import`语句，如果报错，则提示 （如果你在使用 ` JSX`, 请确保文件名称为 `.jsx` 后缀.）\n\n> 也不确定是啥错，先提示一下可能是`JSX`没有提前被编译的错。\n\n\n\n# 307 - a847621 重构简化`web_modules`处理逻辑\n\n没有改造前，代码重复。\n\n![2](2.png)\n\n重构后：\n\n![3](3.png)\n\n> 把重复代码合并\n\n\n\n# 308 - b7f5ad2 [#95](https://github.com/vitejs/vite/pull/95) 使用`brotli-size`输出经`brotli`压缩后的文件大小\n\n新增部分：\n\n- 添加[`brotli-size`](https://www.npmjs.com/package/brotli-size)包（详 **改动一**）\n\n### 改动一\n\nHTTP服务器程序Apache和nginx支持Brotli压缩算法，检测站点有没有使用，可以查看**content-encoding**响应头是否为**br**，**accept-encoding**是包括**br**。\n\n`Brotil`是很常用的压缩算法，可以输出`Brotil`压缩后的大小，[Skypack](https://www.skypack.dev/)也使用该压缩算法。经过基准测试，大约增加了3%的构建时间。\n\n[新的开源压缩算法Brotli](https://zhuanlan.zhihu.com/p/33405940)\n\n\n\n# 309 - 7ffa9c0 [#97](https://github.com/vitejs/vite/pull/95) windows hmr\n\n这是一个windows路径引发的问题，我之前也提到过windows下，`importer`与`importee`关系路径不正确（`serverPluginModuleRewrite.ts`之前已修复）。\n\n![4](4.png)\n\n`importee`的路径错误了（重写你的import语句，根据importer获取其importee的publicPath，所以现在这里是publicPath在windows下错误输出，至于为什么平台没有测试出来，是因为尤大没有做`hot.accept`的`hmr`测试，所以这其实是windows下`serverPluginHmr,ts`改写 引用`hmr`的路径的BUG）。\n\n![6](6.png)\n\n修复位置：\n\n```typescript\n# serverPluginHmr.ts rewriteFileWithHMR registerDep\nconst depPublicPath = slash(path.resolve(path.dirname(importer), e.value))\n```\n\n修复后：\n\n```typescript\nconst depPublicPath = slash(\n      path.isAbsolute(e.value) // 如果是绝对路径，则不做处理，因为它就是publicPath\n        ? e.value\n        : resolveImport({ importer, id: e.value, resolver })\n)\n```\n\n![5](5.png)\n\n`resolveImport`：从`serverPluginModuleRewrite`中抽离改写import语句的功能。\n\n> 主要原因，尤大遗忘漏了。\n\n\n\n# 310 - e98102a 调整`cli.ts`\n\n调整`logHelp`的位置，放在最外层堆。\n\n变量名称修改：`s`-> `start`\n"
  },
  {
    "path": "31-40/commit-31-40.md",
    "content": "# 31 - c45e066 整合```css hmr```\n\n把更新渲染```<style>```的代码，整合到```client```中。省去每次提取```<styele>```模块都需要经过服务器语法词汇分析的过程。\n\n\n\n# 32 - ef95a00 http缓存与读取文件的缓存\n\n### 添加的包\n\n#### ```koa-conditional-get```:\n\nConditional Get 又名 条件式请求 ，常见实现有```Last-Modified``` 和 ```ETag``` 两种。\n\n#### ```koa-etag```:\n\n为```koa```的响应设置```etag-header```。\n\n#### ```lru-cache```:\n\n要搞清楚```LruCache``` 是什么之前，首先要知道 ```Android``` 的缓存策略。其实缓存策略很简单，举个例子，就是用户第一次使用网络加载一张图片后，下次加载这张图片的时候，并不会从网络加载，而是会从内存或者硬盘加载这张图片。\n\n缓存策略分为添加、获取和删除，为什么需要删除缓存呢？因为每个设备都会有一定的容量限制，当容量满了的话就需要删除。\n\n那什么是 ```LruCache```呢？其实```LRU(Least Recently Used)``` 的意思就是近期最少使用算法，它的核心思想就是会优先淘汰那些近期最少使用的缓存对象。\n\n> 作者：一团捞面\n> 链接：https://www.jianshu.com/p/e09870b60046\n\n### ```cacheRead```\n\n弃用```fs.readFile```，转为```cacheRead```。\n\n```cacheRead```封装```fs.readFile```，读取到的文件与文件的上一次更新的时间戳，缓存在```LRUCache```中，下次读取文件的时候，首先在```LRUCache```中寻找。\n\n```lastModified```: https://nodejs.org/api/fs.html#fs_stats_mtimems\n\n```typescript\nconst moduleReadCache = new LRUCache<string, CacheEntry>({\n  max: 10000\n})\n\nexport async function cachedRead(path: string, encoding?: string) {\n  const lastModified = (await fs.stat(path)).mtimeMs\n  const cached = moduleReadCache.get(path)\n  if (cached && cached.lastModified === lastModified) {\n    return cached.content\n  }\n  console.log('reading from disk: ', path)\n  const content = await fs.readFile(path, encoding)\n  moduleReadCache.set(path, {\n    content,\n    lastModified\n  })\n  return content\n}\n```\n\n### http缓存\n\n使用中间件即可。\n\n```typescript\napp.use(require('koa-conditional-get')())\napp.use(require('koa-etag')())\n```\n\n\n\n# 33 - f6ef1b1 进一步利用LRU\n\n编译```.vue```文件的方法：\n\n```parseSFC```、```compilerSFCMain```、``` compileSFCTemplate```与```compileSFCStyle```均把转换得出的结果保存在```vueCache```中。\n\n```typescript\ninterface CacheEntry {\n  descriptor?: SFCDescriptor // parseSFC\n  template?: string // compilerSFCTemplate\n  script?: string // compilerSFCMain\n  styles: string[] // compileSFCStyle\n}\n\nexport const vueCache = new LRUCache<string, CacheEntry>({\n  max: 65535\n})\n```\n\n\n\n# 34 - 052ac90 v0.3.0发布\n\n### 我觉得这个版本能用，```<style>```的流程：\n\n1. 获取```.vue```\n2. 根据```parseSFC```，遍历```style```，每个子```style```均生成语句：```updateStyle(\"92a6df80-0\", \"/Comp.vue?type=style&index=0&t=1617780907326\")```\n3. ```clinet```端，调用```updateStyle```方法，创建出```<link id=\"vite-css-92a6df80-0\" rel=\"stylesheet\" type=\"text/css\" href=\"/Comp.vue?type=style&amp;index=0&amp;t=1617780907326\">```\n4. ```server```端，接收到```/Comp.vue?type=style&amp;index=0&amp;t=1617780907326```\n5. ```type```为```style```，```index```为```0```，```parseSFC```所编译的```AST```语法树```descriptor```，发送```descriptor.styles[index]```的内容给```client```端\n\n### BUG：（```vue```的bug）\n\n当新增```<style scoped>```，再去添加```class```样式不起效。\nhttps://github.com/vuejs/vue-next/issues/3382\n\n\n\n# 35 - 7b75253 304将不再处理内容\n\n```modulesPlugin```中判断请求304，将不再处理内容。\n\n```typescript\nconst internalPlugins: Plugin[] = [\n  modulesPlugin,\n  vuePlugin,\n  hmrPlugin,\n  servePlugin\n]\n\n# modulesPlugin的部分代码\napp.use(async (ctx, next) => {\n    await next()\n    \n    if (ctx.status === 304) {\n      return\n    }\n})\n```\n\n根据洋葱模型，```modulesPlugin```判断```304```处于所有中间件执行的最后一个步骤。\n\n# 36 - 0f88118 v0.3.1\n\nv0.3.1\n\n\n\n# 37 - 2c1b802 删除无用的包\n\n删除```@babel/parser```\n\n\n\n# 38 - 1e4a78c v0.3.2\n\nv0.3.2\n\n# 39 - 6e66766 构建```js-map```，```js```的```hmr```\n\n目的：创建导入文件的关系图，在```hmr```的时候，可以知道应该热加载哪些文件。\n\n### ```rewriteImports```\n\n遇到```/^[^\\/\\.]/.test(id)```，改写成为```__modules/${id}```。\n\n例如：\n```import { ref } from 'vue'``` -> ```import { ref } from '__module/vue'```\n\n如果遇到：\n\n```import { a } from './vue'``` 不做处理，开头非```/```，非```.```的一律不做处理。\n\n通俗来说，我们如何写node引入模块，就把我们引入的路径给改写。\n\n### 构建```js```关系链\n\n![cache](./A@B5FF6KI9XL_FKRY$U80CR.png)\n\n目的是，收集所有需要```hmr```的```非模块文件```。\n\n### ```hmr.ts```\n\n使用```importeeMap```，获得关系链，进行```HMR```的一些东西。\n\n\n\n# 40 - a183791 ```snowPack```\n\nhttps://github.com/vitejs/vite/pull/4\n\n详细可以去看这个```pr```了，支持```snowpack```。\n\n### ```modules.ts```\n\n```typescript\nasync function resolveWebModule(\n  root: string,\n  id: string\n): Promise<string | undefined> {\n  const webModulePath = webModulesMap.get(id)\n  if (webModulePath) {\n    return webModulePath\n  }\n  const importMapPath = path.join(root, 'web_modules', 'import-map.json')\n  if (await fs.stat(importMapPath).catch((e) => false)) {\n    const importMap = require(importMapPath)\n    if (importMap.imports) {\n      const webModulesDir = path.dirname(importMapPath)\n      Object.entries(\n        importMap.imports\n      ).forEach(([key, val]: [string, string]) =>\n        webModulesMap.set(key, path.join(webModulesDir, val))\n      )\n      return webModulesMap.get(id)\n    }\n  }\n}\n```\n\n"
  },
  {
    "path": "311-320/311-320.md",
    "content": "# 311 - c18451d 构建：简化tsconfig\n\n改动部分：\n\n`tsconfig.base.json`：去除默认选项，`esModuleInterop`开启后，`allowSyntheticDefaultImports`会为`true`。\n\n![1](1.png)\n\n[tsconfig](https://www.tslang.cn/docs/handbook/compiler-options.html)\n\n[esModuleInterop](https://zhuanlan.zhihu.com/p/148081795) [allowSyntheticDefaultImports](https://blog.leodots.me/post/40-think-about-allowSyntheticDefaultImports.html): 兼容esm引入cjs\n\n`noImplicitAny`：在表达式和声明上有隐含的 `any`类型时报错，默认`alse`\n\n`strictNullChecks`: 在严格的 `null`检查模式下， `null`和 `undefined`值不包含在任何类型里，只允许用它们自己和 `any`来赋值（有个例外， `undefined`可以赋值到 `void`），默认`false`。\n\n`strict`: 启用所有严格类型检查选项。\n\n`noUnusedLocals`:  若有未使用的局部变量则抛错。\n\n\n\n# 312 - 0708279 重构[#97](https://github.com/vitejs/vite/pull/95)，且全局注册importer与importee关系，即设置`importerMap`\n\n在[309 - 7ffa9c0](https://github.com/Kingbultsea/vite-analysis/blob/master/301-310/301-310.md#309---7ffa9c0-97-windows-hmr)，修复了windows路径改写。\n\n改动部分：\n\n- 因为`resolveImport`方法已经处理过绝对路径，所以现在去除重复的逻辑代码。\n\n- `handleJSReload`会读取`importerMap`作为检测，才会调用`walkImportChain`检测`hmrAcceptanceMap`，才能够触发`js-update`，现在把注册`importerMap`的逻辑放进`serverPluginHmr.ts registerDep`中。\n\n这块有很多坑，`accept`被调用一次以上，新的将会覆盖旧的，`client.ts`处理`hot-accept`用了key value对应关系; importer调用自身作为importee，触发`js-update`，如果其他文件或者组件引入了该文件，将感受不到任何变化（合理？只能在文档中约束不要import使用了HMR API的js文件了）。\n\n> importerMap存放所有文件的importer和importee的关系。\n>\n> hmrAcceptanceMap存放使用了hot.accept的importer和importee的关系，用于触发`js-update`，提高hmr性能体验。\n\n### 关于function的参数风格\n\n之前查阅`vue-next`也发现很少使用解构（或没有），`Vite`里面是完全没有的。\n\n解构可以使我们打乱参数顺序，但是不能变换名称，而且写类型的时候会有重复字段。\n\n```typescript\nexport const resolveImport = (\n  importer: string,\n  id: string,\n  resolver: InternalResolver,\n  timestamp?: string\n): string => {}\n\nexport const resolveImport = ({\n  importer,\n  id,\n  resolver,\n  timestamp\n}: {\n  importer: string\n  id: string\n  resolver: InternalResolver\n  timestamp?: string\n}): string => {}\n```\n\n\n\n# 313 - 5ae6278 changelog\n\n## [0.13.1](https://github.com/vuejs/vite/compare/v0.13.0...v0.13.1) (2020-05-09)\n\n### Bug Fixes\n\n- **hmr:** 修复hot.accept() 路径改写（windows下） ([#97](https://github.com/vuejs/vite/issues/97)) ([7ffa9c0](https://github.com/vuejs/vite/commit/7ffa9c0b953f4a78251a8c379a2edf8e31fd368b))\n- 修复web_modules后缀添加问题 + 在非`jsx`文件下使用`jsx`将会发出警告（反正`es-module-lexer`包报错了就提示是这个错） ([3653793](https://github.com/vuejs/vite/commit/3653793a2f713b126aaefb01b00878614fc4c63c)), closes [#94](https://github.com/vuejs/vite/issues/94)\n\n### Features\n\n- **build:** 输出`brotli-compressed`压缩下的文件大小 ([#95](https://github.com/vuejs/vite/issues/95)) ([b7f5ad2](https://github.com/vuejs/vite/commit/b7f5ad245f10efac89be0954155639e310c46e00))\n\n\n\n# 314 - c057265 v0.13.1\n\nrelease v0.13.1\n\n\n\n# 315 - decbfc2 create-vite-app 可以构建react模板\n\n改动部分：\n\n- package.json: 新增`bin` `cva`，是`create-vite-app`的简写\n- 封装方法，现在有两个模板文件夹`template-vue`和`template-preact`，默认`--template vue`\n\n> [`util.promisify`]()是在`node.js 8.x`版本中新增的一个工具，用于将老式的`Error first callback`转换为`Promise`对象，让老项目改造变得更为轻松。\n\n\n\n# 316 - 32d6341 create-vite-app v1.1.0\n\nrelease create-vite-app v1.1.0\n\n> **记住**（之前有提到）启动yarn publich会执行`prepublishOnly` 即 `node updateVersions.js`，自动同步模板里devDependencies的`vite`版本\n\n![2](2.png)\n\n\n\n# 317 - 122851e [#100](https://github.com/vitejs/vite/pull/100) 调整上传到npm的文件包括范围\n\n```json\n{\n    \"files\": [\n        \"index.js\",\n        \"template-*\"\n    ]\n}\n```\n\n> [files](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files)，默认ignored，包括文件、目录或全局模式。\n\n\n\n# 318 - e00bf3a  [#98](https://github.com/vitejs/vite/pull/98) 增强服务开启提示，保持与`vue-cli`一致\n\n![3](3.png)\n\n\n\n# 319 - 6d2fabe create-vite-app v1.1.1\n\nrelease create-vite-app v1.1.1\n\n\n\n# 320 - 9336dac [#106](https://github.com/vitejs/vite/pull/106) html标签`<script src=''>`支持单引号\n\n```typescript\nconst srcRE = /\\bsrc=(?:\"([^\"]+)\"|'([^']+)'|([^'\"\\s]+)\\b)/\n// 获取src的正则\n\n// 修改后：默认srcAttr[2]\nconst srcAttr = openTag.match(srcRE)\n            if (srcAttr) {\n              // register script as a import dep for hmr\n              const importee = cleanUrl(\n                slash(path.resolve('/', srcAttr[1] || srcAttr[2]))\n              )\n              debugHmr(`        ${importer} imports ${importee}`)\n              ensureMapEntry(importerMap, importee).add(importer)\n            }\n```\n\n> 发现了一个BUG，当index.html的`<script src=\"./main.js\">拥有代码</script>`，将不会记录index.html与main.js的importer importee关系；我觉得没有任何关系，这里的代码完全去除不用注册也行（如果为了规范那无视），因为`hmr`识别到顶层没有parent，是会进行页面刷新的，即`walkImportChain` 设置`hasDeadEnd`为`true`。\n\n"
  },
  {
    "path": "321-330/321-330.md",
    "content": "# 321 - 6ee0168 [#105](https://github.com/vitejs/vite/pull/105) 修复浏览器无法打开的问题\n\n主要是因为`ececa`包被放进了`devDependencies`，从而没有找到该包报错。\n\n现在把`execa`包移动进`dependencies `。\n\n\n\n# 322 - 32cf37f [#107](https://github.com/vitejs/vite/issues/107) 处理小写的`doctype`\n\n**issues**: `<!doctype html>`会引起`vite build`崩溃。\n\n原因：`@vue/compiler-core`没有处理小写的`doctype`为注释（按照标准）。\n\n![1](1.png)\n\n改动部分：\n\n- `build/buildPluginHtml.ts`，替换`html`内容，把`doctype`改为大写，即`html.replace(/<!doctype\\s/i, '<!DOCTYPE ')`\n\n> 写个框架要注意的事情真的好多...\n\n\n\n# 323 - 6776e76 防止jest警告的程序包名称不明确\n\n`template-preact`: `package.json` `name`改为`vite-preact-starter`\n\n`template-vue`: `package.json` `name`改为`vite-starter`\n\n> 在`vite`的测试流没有看到，应该是尤大想知道开发者用了哪个模板\n\n\n\n# 324 - 2320d3e changelog\n\n## [0.13.2](https://github.com/vuejs/vite/compare/v0.13.1...v0.13.2) (2020-05-09)\n\n### Bug Fixes\n\n- -修复-open标志引起的错误 ([#105](https://github.com/vuejs/vite/issues/105)) ([6ee0168](https://github.com/vuejs/vite/commit/6ee016892d7b375cc8dd8cbc4dc10c03325d4dc8))\n- 处理小写的doctypes (close [#107](https://github.com/vuejs/vite/issues/107)) ([32cf37f](https://github.com/vuejs/vite/commit/32cf37fd5125be7dd3b65de2024e89685d7cbc8e))\n- 支持`<script src>`使用单引号  ([#106](https://github.com/vuejs/vite/issues/106)) ([9336dac](https://github.com/vuejs/vite/commit/9336dacaeaae37bd2adf36ab1816c063eddbd4eb))\n- cva: package.json files 包括 template-* ([#100](https://github.com/vuejs/vite/issues/100)) ([122851e](https://github.com/vuejs/vite/commit/122851ee802c8e6374be42e704883e6ed91b0b02))\n\n### Features\n\n- 增强cli输出的信息，保持与vue-cli一样 ([#98](https://github.com/vuejs/vite/issues/98)) ([e00bf3a](https://github.com/vuejs/vite/commit/e00bf3a7fb029416c394e2606a3ce4ed8f3079b1))\n- **cva:** 支持多种类型的模板 ([decbfc2](https://github.com/vuejs/vite/commit/decbfc2ee9e0c88c9e94a8f4f39032cdf5b5d6c5))\n\n\n\n# 325 - b5cf006 v0.13.2\n\nrelease v0.13.2\n\n\n\n# 326 - 49e79e7 支持`build --ssr`\n\n改动部分：\n\n- `build/index.ts`：新增`ssr`选项，调用`ssrBuild`对`options`改造，输出的文件夹名称为`dist-ssr`。\n\n> 不做深入探究`ssr`（因为我不熟悉，也不想深入熟悉┭┮﹏┭┮！！ 知道它是在服务端完成初始`vnode` -> `html`即可）[207](https://github.com/Kingbultsea/vite-analysis/blob/d0e223924b0656785fa14ac073ff78dcdfef818a/201-210/201-210.md#207---4808f41-build%E6%94%AF%E6%8C%81ssr)\n\n\n\n# 327 - d6151bf [#104](https://github.com/vitejs/vite/pull/104) 为插件开发者暴露`rewriteImports` 方法\n\n**pikax**: 我在编写一个基于路由的SSR插件，我需要编译SFC文件，并构建像`vite`的脚本。但是`vite`依赖中间件处理SFC文件路径。\n\n**尤大**：为了一致性起见，我认为我们要么公开所有 `compileSFC*` 方法，要么不公开它们。但是，将它们公开意味着我们不能再将它们视为内部结构并自由调整参数。假设它们仅在开发服务器中使用，有时可能需要 `Koa context` - 这将使它们在其他地方无法使用。我认为直到`vite`变得更稳定更安全之前，你可以复制逻辑黏贴到你的插件中。\n\n**pikax**：同意，在我的情况下，我只需要样式，其他的我基本上复制了逻辑。 `rewriteImports` 是否适合公开？\n\n**尤大**：可以，`rewriteImports`可能是安全的，尽管我现在认为它是半内部的。你应该做好它可能会坏了的心理准备;)\n\n![2](2.png)\n\n\n\n# 328 - ab940fd 支持`config`文件配置`vite`\n\n改动部分\n\n- 新增`src/node/config.ts`: 提供`ServerConfig`, `BuildConfig`, `resolveConfig`给`vite`，即用户可以通过`config.ts`导入`vite`类型（详 **新增一**）\n- `cli.ts`：**合并来自命令行和config文件的参数**，config文件的优先级高于命令行；一切类型都为可选项，所以可以作为`runServe( options: ServerConfig & {port?: numberopen?: boolean})`和`runBuild(options: BuildConfig)`的**参数**（详 **改动二**）\n- `playground`，新增`tsconfig.json`和`vite.config.ts`（详 **改动三**）\n\n> 建议补充一下tree-shaking（\"遇到的难题\"），webpack4和rollup都在用它 [`sideEffects`](https://zhuanlan.zhihu.com/p/41795312)。\n\n## 新增一\n\n- 利用rollup与`esbuild`编译`ts`类型的`config`，不做`tree-shaking`。\n- `UserConfig`暴露了所有`BuildConfig`字段，其中设定`plugins`字段为：`SharedConfig`的`alias`(todo)、`transform`(todo)和`resolvers`（id public file路径映射）；`BuildConfig`的`rollupInputOptions`(`rollup.rollup()`参数，即构建前的配置)、`rollupOutputOptions`(`bundle.generate()`的参数，即生成配置)和`rollupPluginVueOptions`(`rollup-plugin-vue`插件参数)。\n\n**根据定义**`Plugin`，尤大想暴露一个`vite`特性的插件给用户在`config.ts.plugins`调用，插件能帮助用户**配置`rollup`**，设置（开发&构建）**路径映射**、（开发&构建）代码转换、**`rollup-plugin-vue`配置**和（开发）**`koa`中间件**。\n\n用户能做的事(`UserConfig`)：\n\n- 一切构建选项\n- 插拔式配置`vite`插件\n\n```typescript\n# src/node/config.ts\n\nimport { ServerPlugin } from './server'\nimport { Resolver } from './resolver'\nimport {\n  InputOptions as RollupInputOptions,\n  OutputOptions as RollupOutputOptions\n} from 'rollup'\nimport { Options as RollupPluginVueOptions } from 'rollup-plugin-vue'\n\n/**\n * server与build的共享选项.\n */\nexport interface SharedConfig {\n  /**\n   * 项目根目录. 可以是绝对路径（从盘开始的路径）,\n   * 或者相对于config所在目录的相对路径\n   * @default process.cwd()\n   */\n  root?: string\n  /**\n   * TODO\n   */\n  alias?: Record<string, string>\n  /**\n   * TODO\n   */\n  transforms?: Transform[]\n  /**\n   * 路径映射\n   */\n  resolvers?: Resolver[]\n  /**\n   * 为jsx 设置 factory 和 fragment.\n   * @default\n   * {\n   *   factory: 'React.createElement',\n   *   fragment: 'React.Fragment'\n   * }\n   */\n  jsx?: {\n    factory?: string\n    fragment?: string\n  }\n}\n\n// koa 中间件\nexport interface ServerConfig extends SharedConfig {\n  plugins?: ServerPlugin[]\n}\n\nexport interface BuildConfig extends SharedConfig {\n  /**\n   * https://github.com/Kingbultsea/vite-analysis/blob/8358749dc960a93262458eced9868d593c58af8d/211-220/211-220.md#212---c82a597-%E6%89%93%E5%8C%85%E5%90%8E%E7%9A%84%E5%BC%95%E5%85%A5%E8%B7%AF%E5%BE%84%E5%8F%AF%E9%85%8D%E7%BD%AE%E5%8C%96\n   * Base public path when served in production.\n   * @default '/'\n   */\n  base?: string\n  /**\n   * vite打包后的路径，如果存在该路径则会删除整个文件夹\n   * @default 'dist'\n   */\n  outDir?: string\n  /**\n   * 相对于outDir的js/css/image存放路径\n   * @default 'assets'\n   */\n  assetsDir?: string\n  /**\n   * 小于该设置大小的文件，将被设置为行内资源，单位byte\n   * @default 4096 (4kb)\n   */\n  assetsInlineLimit?: number\n  /**\n   * 是否生成sourcemap\n   * @default false\n   */\n  sourcemap?: boolean\n  /**\n   * 指定js压缩方式\n   * 可选项'terser' 或 'esbuild' 'false'\n   * @default 'terser'\n   */\n  minify?: boolean | 'terser' | 'esbuild'\n  /**\n   * 用于服务器端渲染的构建\n   * @default false\n   */\n  ssr?: boolean\n\n  // 以下仅为API，未记录在CLI中. -----------------\n  /**\n   * rollup.rollup()\n   * https://rollupjs.org/guide/en/#big-list-of-options\n   */\n  rollupInputOptions?: RollupInputOptions\n  /**\n   * bundle.generate()\n   * https://rollupjs.org/guide/en/#big-list-of-options\n   */\n  rollupOutputOptions?: RollupOutputOptions\n  /**\n   * rollup-plugin-vue\n   * https://github.com/vuejs/rollup-plugin-vue/blob/next/src/index.ts\n   */\n  rollupPluginVueOptions?: Partial<RollupPluginVueOptions>\n  /**\n   * 是否输出资源生成信息\n   * @default false\n   */\n  silent?: boolean\n  /**\n   * 是否将文件写入磁盘\n   * @default true\n   */\n  write?: boolean\n  /**\n   * 是否写入index.html到磁盘中&注入\n   * @default true\n   */\n  emitIndex?: boolean\n  /**\n   * 是否写入资源到磁盘（不包括js文件）\n   * @default true\n   */\n  emitAssets?: boolean\n}\n\n// 暴露给用户使用的配置\nexport interface UserConfig extends BuildConfig {\n  plugins?: Plugin[]\n}\n\nexport type Condition = RegExp | RegExp[] | (() => boolean)\n\nexport interface Transform {\n  include?: Condition\n  exclude?: Condition\n  query?: Condition\n  /**\n   * @default 'js'\n   */\n  as?: 'js' | 'css'\n  transform?: (code: string) => string | Promise<string>\n}\n\nexport interface Plugin\n  extends Pick<SharedConfig, 'alias' | 'transforms' | 'resolvers'>,\n    Pick<\n      BuildConfig,\n      'rollupInputOptions' | 'rollupOutputOptions' | 'rollupPluginVueOptions'\n    > {\n  configureServer?: ServerPlugin\n}\n\nimport path from 'path'\nimport fs from 'fs-extra'\nimport chalk from 'chalk'\nimport type Rollup from 'rollup'\nimport { createEsbuildPlugin } from './build/buildPluginEsbuild'\n\n// 处理config\nexport async function resolveConfig(\n  configPath: string | undefined\n): Promise<UserConfig | undefined> {\n  const start = Date.now()\n  const resolvedPath = path.resolve(\n    process.cwd(),\n    configPath || 'vite.config.js'\n  )\n  try {\n    if (await fs.pathExists(resolvedPath)) {\n      const isTs = path.extname(resolvedPath) === '.ts'\n      // 1. 尝试加载非config.ts文件\n      if (!isTs) {\n        try {\n          return require(resolvedPath)\n        } catch (e) {\n          if (\n            !/Cannot use import statement|Unexpected token 'export'/.test(\n              e.message\n            )\n          ) {\n            throw e\n          }\n        }\n      }\n\n      // 2. ts文件有可能使用import语法\n      // 使用rollup转译es import语法为require语法\n      const rollup = require('rollup') as typeof Rollup\n      const esbuilPlugin = await createEsbuildPlugin(false, {})\n      const bundle = await rollup.rollup({\n        // .json与非. 且非绝对路径 设置为外部引入\n        // 意思就是不处理网络请求和json文件\n        external: (id: string) =>\n          (id[0] !== '.' && !path.isAbsolute(id)) ||\n          id.slice(-5, id.length) === '.json',\n        input: resolvedPath,\n        treeshake: false,\n        plugins: [esbuilPlugin]\n      })\n\n      const {\n        output: [{ code }]\n      } = await bundle.generate({\n        exports: 'named',  // esm使用named 可以解构 https://rollupjs.org/guide/en/#outputexports\n        format: 'cjs'\n      })\n\n      const config = await loadConfigFromBundledFile(resolvedPath, code)\n      // config.root规范为绝对路径 https://blog.csdn.net/kikyou_csdn/article/details/83150538\n      if (config.root && !path.isAbsolute(config.root)) {\n        config.root = path.resolve(path.dirname(resolvedPath), config.root)\n      }\n\n      require('debug')('vite:config')(\n        `config resolved in ${Date.now() - start}ms`\n      )\n      console.log(config)\n      return config\n    }\n  } catch (e) {\n    console.error(\n      chalk.red(`[vite] failed to load config from ${resolvedPath}:`)\n    )\n    console.error(e)\n    process.exit(1)\n  }\n}\n\ninterface NodeModuleWithCompile extends NodeModule {\n  _compile(code: string, filename: string): any\n}\n\nasync function loadConfigFromBundledFile(\n  fileName: string,\n  bundledCode: string\n) {\n  const extension = path.extname(fileName)\n  const defaultLoader = require.extensions[extension]!\n  require.extensions[extension] = (module: NodeModule, filename: string) => {\n    if (filename === fileName) {\n      ;(module as NodeModuleWithCompile)._compile(bundledCode, filename)\n    } else {\n      defaultLoader(module, filename)\n    }\n  }\n  delete require.cache[fileName]\n  const raw = require(fileName)\n  const config = raw.__esModule ? raw.default : raw\n  require.extensions[extension] = defaultLoader\n  return config\n}\n```\n\n> [import type](https://segmentfault.com/a/1190000039800522)，仅仅导入类型，避免导入类型且导出该类型时，babel报错。以前通过开启`isolatedModules`提醒禁止这种用法，要使用显示类型导出&显示类型导入。\n\n## 改动二\n\n现在可以直接通过`--debug`标志来打开`debug`信息了，当然你也可以`--debug resolve `只查看路径映射信息。\n\n```typescript\n// make sure to set debug flag before requiring anything\nif (argv.debug) {\n  process.env.DEBUG = `vite:` + (argv.debug === true ? '*' : argv.debug)\n}\n```\n\n## 改动三\n\n```json\n{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../\", // 基本路径\n    \"paths\": {\n      \"vite\": [\"dist/index.d.ts\"] // vite类型路径 即 ../dist/index.d.ts\n    }\n  }\n}\n```\n\n```typescript\nimport type { UserConfig } from 'vite' // baseUrl + paths.vite[0]\n\nconst config: UserConfig = {\n  jsx: {\n    factory: 'h',\n    fragment: 'Fragment'\n  },\n  minify: false\n}\n\nexport default config\n```\n\n\n\n# 329 - 7cdaa0b 构建时支持配置`rollup-plugin-vue`&`serverPluginVue` 的`compilerOptions`选项\n\n改动部分：\n\n- `serverPluginVue.ts`&`build/index.ts.rollup-plugin-vue`:  用户可以配置`compilerOptions`来影响`vue`对`<template>`标签的`vnode render`转换（**改动一**）\n- 插件处理`jsx`，直接获取通过用户的`config.jsx.factory` & `config.jsx.fragment`获取\n- `node/config.ts`: 用户配置新增`configureServer: ServerPlugin`，即用户可以配置`koa`中间件；Plugin插件新增`vueCompilerOptions`（即**改动一**）能力；Plugin处理器，即合并用户config（详 **改动三**）;修复return BUG。\n\n## 改动三\n\n每个插件的功能将会被合并到config中，倒序优先（alias除外，config配置文件优先），**`Plugin`插件后者覆盖前者重复字段**。\n\n```typescript\nfunction resolvePlugin(config: UserConfig, plugin: Plugin): UserConfig {\n  return {\n    alias: {\n      ...plugin.alias,\n      ...config.alias\n    },\n    transforms: [...(config.transforms || []), ...(plugin.transforms || [])],\n    resolvers: [...(config.resolvers || []), ...(plugin.resolvers || [])],\n    configureServer: (ctx) => {\n      if (config.configureServer) {\n        config.configureServer(ctx)\n      }\n      if (plugin.configureServer) {\n        plugin.configureServer(ctx)\n      }\n    },\n    vueCompilerOptions: {\n      ...config.vueCompilerOptions,\n      ...plugin.vueCompilerOptions\n    },\n    rollupInputOptions: {\n      ...config.rollupInputOptions,\n      ...plugin.rollupInputOptions\n    },\n    rollupOutputOptions: {\n      ...config.rollupOutputOptions,\n      ...plugin.rollupOutputOptions\n    }\n  }\n}\n```\n\n> 输出空白的console.log()可以达到换行效果。\n>\n> 用户可以配置config文件的额外字段，作为服务插件的参数传递（非`koa`上下文）。\n\n\n\n# 330 - a66ac5b 更新 issue模板\n\n**328 - ab940fd** 中的改动二，配置了`--debug`，现在需要更新issue模板，告诉反馈者使用`--debug`打开调试信息。\n\n"
  },
  {
    "path": "331-340/331-340.md",
    "content": "# 331 - 6cf1e31 issue 模板\n\n## Before you continue...\n\n如果你升级`Vite`后，出现了问题，尝试使用浏览器删除缓存，勾选“Disable cache\"\n\n\n\n# 332 - a4524b4 修复`hmr hot.on`回调参数类型\n\n`on`可以绑定事件名称和回调的关系，`hmr` `custom`事件可以根据server端传入的`id`事件名称，触发回调。\n\n![1](1.png)\n\n\n\n# 333 - 86d550a 支持`config` `alias`\n\n改动部分：\n\n- `node/resolver.ts`：去除`idToRequest`，新增`alias`（详 **改动一**）\n- `node/server/index.ts`加入`alias`\n- `node/build/index.ts`加入`alias`\n- `node/config.ts`输出`export { Resolver }`类型（详 **改动四**）\n\n## 改动一\n\n1. **废弃**`idToRequest`，该功能可以帮助我们id转换为request路径（开发和构建都用到），即import a from 'id' -> import a from '/@modules/id'，我们可以操控其转换为'/@modules/id' -> '/@modules/aid'。简单来说就是`vite`需要改写你的路径，你使用`idToRequest`拦截了该行为。\n\n> 别问为什么要改写路径，不改写浏览器请求到时候重新传入`vite`分析不了你的模块类型，`import 'a'` 和 `import '/a'`，给到浏览器请求回来都是`localhost:8080/a`，然鹅前者是模块，后者是绝对路径的a文件。不信你自己试试🤨，反正我没试，蛤蛤蛤。\n\n2. 新增`alias`，顶替`idToRequest`作用，传入的参数是对象，即映射id -> 用户想要的id，。\n\n```typescript\nexport function createResolver(\n  root: string,\n  resolvers: Resolver[],\n  alias: Record<string, string> // 新增参数\n): InternalResolver {\n  return {\n    // ...  \n    alias: (id: string) => {\n      let aliased: string | undefined = alias[id]\n      if (aliased) {\n        return aliased\n      }\n      for (const r of resolvers) {\n        aliased = r.alias && r.alias(id)\n        if (aliased) {\n          return aliased\n        }\n      }\n    }\n  }\n}\n```\n\n#### `idToRequest`不就可以了？为什么要alias?\n\n`idToRequest`需要映射后的路径符合`vite`的改写，开发者使用起来**不方便**（开发者不一定会看源码，需要提醒添加`@modules/`），比如我想要改写`a`为`b`模块，我需要返回`@modules/b`。（构建模式下，`@/modules`会被`requestToFile`去除）\n\n`alias`，利用对象做映射关系，且改版后（不是alias），开发者不需要添加`@modules`了。\n\n## 改动四\n\n输出Resolver类型，提供用户定义Resolver。\n\n```typescript\nexport interface Resolver {\n  requestToFile(publicPath: string, root: string): string | undefined\n  fileToRequest(filePath: string, root: string): string | undefined\n  alias?(id: string): string | undefined\n}\n```\n\n\n\n# 334 - b85de93 修复加载ts类型的config\n\n由于先前判断用户没有设置`configPath`，默认为`vite.config.js`，然后利用`await fs.pathExists(resolvedPath)`寻找是否有该文件，没有则不做任何事情。\n\n现在判断有没有`js`再判断有没有`ts`就好。\n\n![2](2.png)\n\n\n\n# 335 - b7b9d85 添加alias测试\n\n注意哈，该测试写了映射为`/aliased`，所以不是模块，不要误会认为`alias`不会被当作模块。\n\n```typescript\n# vite.config.ts\nalias: {\n    alias: '/aliased'\n}\n\n# TestAlias.vue\nimport { msg } from 'alias'\n\n# aliased\nexport const msg = 'alias works.'\n```\n\n\n\n# 336 - 87ee998 支持transform config / vite插件\n\n支持通过**Vite特色的plugins**改变代码，先看实现功能目标测试例子：\n\n```typescript\n# vite.config,ts\nimport type { UserConfig } from 'vite'\nimport { sassPlugin } from './plugins/sassPlugin'\nimport { jsPlugin } from './plugins/jsPlugin'\n\nconst config: UserConfig = {\n  alias: {\n    alias: '/aliased'\n  },\n  jsx: {\n    factory: 'h',\n    fragment: 'Fragment'\n  },\n  minify: false,\n  plugins: [sassPlugin, jsPlugin]\n}\n\nexport default config\n\n# jsPlugin.js vite特色插件\nexport const jsPlugin = {\n  transforms: [\n    {\n      test(id) {\n        return id.endsWith('testTransform.js')\n      },\n      transform(code) {\n        return code.replace(/__TEST_TRANSFORM__ = (\\d)/, (matched, n) => {\n          return `__TEST_TRANSFORM__ = ${Number(n) + 1}`\n        })\n      }\n    }\n  ]\n}\n\n# sassPlugin.js vite特色插件\nimport sass from 'sass'\n\nexport const sassPlugin = {\n  transforms: [\n    {\n      as: 'css',\n      test(id) {\n        return id.endsWith('.scss')\n      },\n      transform(code) {\n        return sass\n          .renderSync({\n            data: code\n          })\n          .css.toString()\n      }\n    }\n  ]\n}\n\n# TestTransform.vue\n<template>\n  <h2>Transforms</h2>\n  <div class=\"transform-scss\">This should be cyan</div>\n  <div class=\"transform-js\">{{ transformed }}</div>\n</template>\n\n<script>\nimport './testTransform.scss'\nimport { __TEST_TRANSFORM__ } from './testTransform.js'\n\nexport default {\n  data() {\n    return {\n      transformed: __TEST_TRANSFORM__\n    }\n  }\n}\n</script>\n```\n\n> 该示例配置了两个vite的插件，想通过`sass`转换`.scss`文件代码，转换特定变量。\n\n改动部分：\n\n- `node/cli.ts`:  build & dev 统一使用`UserConfig`选项类型（`BuildConfig`没有`ServerConfig`的`plugins`，即没有vite特色的koa插件），合并没有任何影响，全是可选项，字段没没有冲突，不管是build还是dev，只负责自己的字段就好了。\n- `node/build/buildPluginCss.ts`:  识别新参数`transforms: Transform[]`传入，筛选出符合`Transform.as === ‘css’`的`Transform`，再次符合筛选`Transform.test(路径)`；符合`as`和`test`方法的Transform，即可调用`Transform.transform`改变css文件。\n- `node/build/index.ts`：新增选项`transform`（`SharedConfig`已经有了，所以现在只要取就可以了，前几个commit讲解过），该选项交给`buildPluginCss.ts`插件使用，即提供给css资源构建处理器使用，也就是rollup插件， ` transform`生命周期钩子; **除此之外还帮助用户封装**带有`transform`的`rollup。`\n- `node/server/index.ts`: 同build一样，新增`transform`选项，**帮助用户封装仅带有koa洋葱模型插件功能的vite特色插件**（详 主要补充一下洋葱模型的执行顺序 **改动四**）。\n- `node/server/serverPluginCss.ts`: `config.trnasforms`转换`css`，和`buildPluginCss.ts`行为一致。对`config.transforms.test`匹配到的文件进行监听。\n- `node/server/serverPluginHmr.ts`:  `config.transform` `test`字段匹配到的`.XXX`文件，不会触发`handleJSReload`，仅`.module.css`变动需要触发`handleJSReload`（因为是`js`，注册关系在`moduleRewritePlugin`完成，该插件在第三层外层，目前来说是最后执行，交给`cssPlugin`设置`ctx.type = 'js'`）------> **反正意思就是`.modules.css`才触发，经过转换的`.XXX`视为普通`.css`文件。** 看新增七，有说明为什么要这么做。\n- 新增`node/transform.ts`，封装用户传入的config.transforms，转换为`dev`和`build`插件。（详 **新增七**）\n\n> rollup插件是串行执行的，即使是async，也会等待上一个transform的执行完毕再执行下一个transform，文档上有点不清晰（毕竟中华文化博大精深~~~）[transform resolveId](https://github.com/Kingbultsea/vite-analysis/blob/842e5ef132fa68d5e4fa8dcf480cd444b4b0c8f3/111-120/commit-111-120.md#112---1b0b4ba-%E9%85%8D%E7%BD%AE%E5%8C%96%E6%9E%84%E5%BB%BA)\n\n### 改动四\n\n#### 洋葱模型执行顺序（更新-1）\n\nhmrPlugin被提高到内置插件的第一层，[eab49a4](https://github.com/Kingbultsea/vite-analysis/blob/ea4fb552986c95f4da44839e03a00192ce424139/291-300/291-300.md)。注意，文件名称命名与插件名称不一样。\n\n```typescript\nimport { serveStaticPlugin } from './serverPluginServeStatic'\nimport { assetPathPlugin } from './serverPluginAssets'\nimport { cssPlugin } from './serverPluginCss'\nimport { jsonPlugin } from './serverPluginJson'\nimport { esbuildPlugin } from './serverPluginEsbuild'\nimport { vuePlugin } from './serverPluginVue'\nimport { moduleRewritePlugin } from './serverPluginModuleRewrite'\nimport { moduleResolvePlugin } from './serverPluginModuleResolve'\nimport { hmrPlugin, HMRWatcher } from './serverPluginHmr'\n\n# node/server.ts\nconst internalPlugins: Plugin[] = [\n  ...config.plugins,     // 洋葱模型的第一层  （自定）\n  hmrPlugin,             // 洋葱模型的第二层  （里层）  \n  moduleRewritePlugin,   // 洋葱模型的第三层  （外层） --\n  moduleResolvePlugin,   // 洋葱模型的第四层  （里层）\n  vuePlugin,             // 洋葱模型的第五层  （内层）\n  esbuildPlugin,         // 洋葱模型的第六层  （外层） --\n  jsonPlugin,            // 洋葱模型的第七层  （外层） --\n  cssPlugin,             // 洋葱模型的第八层  （外层） --\n  assetPathPlugin        // 洋葱模型的第九层  （里层） \n  ServerTransformPlugin, // 洋葱模型的第十层  （外层） -- \n  serveStaticPlugin      // 洋葱模型的第十一层（里层）\n]\n```\n\n`hmrPlugin`: 初始化ws；**洋葱模型**中，仅发送`path.resolve(__dirname, '../client.js')`文件\n\n`moduleRewritePlugin`:  **洋葱模型**中，改写`import`语句、发送被改造后的`index.html`\n\n`moduleResolvePlugin`: **洋葱模型**中，处理`vue`、 `web_modules` 和 `node_modules`与跳转\n\n`vuePlugin`: **洋葱模型**中，处理SFC组件，ts调用`esbuild`做处理（**sourcemap失效，没有合并**）\n\n`esbuildPlugin`: **洋葱模型**中，转换`.ts`文件，且调用了`genSourceMapString`，有效的`sourcemap`\n\n`jsonPlugin`: **洋葱模型**中转换`json`为`esm`语法的对象\n\n`cssPlugin`:  监听css文件变动（非SFC的src，这在`vuePlugin`中处理变动，即捆绑css与SFC文件关系，css文件变动，获取SFC路径后设置id，触发`vue-style-update`，更新`<link href=\"XXX\">`的`href`链接），监听`config.transforms`变动；**洋葱模型**中处理import类`.modules.css`esm化、css预处理器\n\n`assetPathPlugin`: **洋葱模型**中，识别到是`import`静态资源类型的请求，将数据esm化\n\n`ServerTransformPlugin`：根据`config.transform`动态创建，一个代码转换器，**vite特性插件**，凡是经过该插件处理的，`koa.ctx`上下文会带有`_transformed = true`属性;**洋葱模型**中担当用户自定义转换代码的职责。\n\n> 这里指的里层是比外层先执行\n\n#### 补充[eab49a4](https://github.com/Kingbultsea/vite-analysis/blob/ea4fb552986c95f4da44839e03a00192ce424139/291-300/291-300.md)，提升`hmrPlugin`到内置插件第一层的动机\n\n`WebSocket`初始化，`watcher`参数初始化。\n\n为什么用户的plugins是第一层？覆盖用户对`watcher`的定义（是的，我想不出其他影响了）。\n\n```typescript\n// start a websocket server to send hmr notifications to the client\n  const wss = new WebSocket.Server({ server })\n  const sockets = new Set<WebSocket>()\n\n  wss.on('connection', (socket) => {\n    debugHmr('ws client connected')\n    sockets.add(socket)\n    socket.send(JSON.stringify({ type: 'connected' }))\n    socket.on('close', () => {\n      sockets.delete(socket)\n    })\n  })\n\n  wss.on('error', (e: Error & { code: string }) => {\n    if (e.code !== 'EADDRINUSE') {\n      console.error(chalk.red(`[vite] WebSocket server error:`))\n      console.error(e)\n    }\n  })\n\n  watcher.handleVueReload = handleVueReload\n  watcher.handleJSReload = handleJSReload\n  watcher.send = send\n```\n\n> 我个人认为这个提升没什么实际作用，提前建立WebSocket罢了。\n\n### 新增七\n\n**规范**vite特色插件，这两个插件都会被丢进插件组中。\n\ndev: `...(transforms.length ? [createServerTransformPlugin(transforms)] : [])`\n\nbuild: `...(transforms.length ? [createBuildJsTransformPlugin(transforms)] : [])`\n\n> as?: 'js' | 'css' // 类型，如是css 将会与sPlugin buildPluginCss一同处理，serverPluginHmr仅仅是过滤掉这些实际是css的文件，就怕其他文件也引入了触发了handleJSReload（**尤大防了一手**~）。\n>\n> test.query 在dev中会携带时间参数t\n\n```typescript\nimport { ServerPlugin } from './server'\nimport { Plugin as RollupPlugin } from 'rollup'\nimport { parseWithQuery, readBody, isImportRequest } from './utils'\n\nexport interface Transform {\n  /**\n   * @default 'js'\n   */\n  as?: 'js' | 'css' // 类型，如是css 将会与sPlugin buildPluginCss一同处理\n  test: (\n    path: string,\n    query: Record<string, string | string[] | undefined> // 路径参数 import a from 'a.css?a=2' 在dev中会携带时间参数t\n  ) => boolean\n  transform: (code: string, isImport: boolean) => string | Promise<string>\n}\n\nexport function normalizeTransforms(transforms: Transform[]) {}\n\n// vite特性的koa插件\nexport function createServerTransformPlugin(\n  transforms: Transform[]\n): ServerPlugin {\n  return ({ app }) => {\n    app.use(async (ctx, next) => {\n      await next()\n      for (const t of transforms) {\n        if (t.test(ctx.path, ctx.query)) {\n          ctx.type = t.as || 'js'\n          if (ctx.body) {\n            const code = await readBody(ctx.body)\n            if (code) {\n              ctx.body = await t.transform(code, isImportRequest(ctx))\n              ctx._transformed = true\n            }\n          }\n        }\n      }\n    })\n  }\n}\n\n// rollup的插件\nexport function createBuildJsTransformPlugin(\n  transforms: Transform[]\n): RollupPlugin {\n  transforms = transforms.filter((t) => t.as === 'js' || !t.as)\n\n  return {\n    name: 'vite:transforms',\n    async transform(code, id) {\n      const { path, query } = parseWithQuery(id)\n      for (const t of transforms) {\n        if (t.test(path, query)) {\n          return t.transform(code, true)\n        }\n      }\n    }\n  }\n}\n```\n\n![dev下的timestamp参数t](3.png)\n\n### 总结\n\n添加transform为了开发者更加便捷，用rollup插件和vite特色的koa插件也可以“完成”，但是难度巨大，比如as: css取消hmr是做不到的，跨插件是不可能完成的。\n\n顺带一提，vite特色插件文件是不会被添加进hmr的，每次修改需要重新打开服务（可能有点难以小改动做到，把这些插件的路径保存起来，hmr新增一个handleVitePlugin，需要更新已经调用的`app.use`）。\n\n\n\n# 337 - bf4d394 [#110](https://github.com/vitejs/vite/pull/110) 代码整理\n\n![4](4.png)\n\n\n\n# 338 - ed5b9e7 [#113](https://github.com/vitejs/vite/pull/110) fix: transform应该调用所有的插件\n\n算是个小失误。\n\n```typescript\n# node/transform.ts  createBuildJsTransformPlugin\nfor (const t of transforms) {\n    if (t.test(path, query)) {\n      return t.transform(code, true)\n    }\n}\n\n// 修改后：\nlet result: string | Promise<string> = code\n  for (const t of transforms) {\n    if (t.test(path, query)) {\n      result = await t.transform(result, true)\n    }\n}\nreturn result\n```\n\n\n\n# 339 - 9adbfdb 修复测试环境下的tsconfig\n\n![5](5.png)\n\n解决编辑器提示报错：\n\n![6](6.png)\n\n\n\n# 340 - d6dd2f0\n\n改动部分：\n\n- `node/server/serverPluginVue.ts`: `generateCodeFrame`统一在`resolveCompiler`方法中获取。\n- `node/esbuildService.ts`: 懒加载`generateCodeFrame`（直接包内获取）。\n\n"
  },
  {
    "path": "341-350/341-350.md",
    "content": "# 341 - efc853f 对于jsx|tsx自动引入`jsxFactory `&`Fragment`，新增VUE版JSX处理器\n\n改动部分：\n\n- `node/config.ts`：jsx选项新增`'vue' | 'preact' | 'react'`（详 **改动一**）\n- `node/esbuildService.ts`：自动添加`h ` & `Fragment`，新增`resolveJsxOptions`方法（详 **改动二**）\n- `client/vueJsxCompat.ts`（client端，首次新增新脚本）：为了能vue也能享受到(j|t)sx，vue的虚拟dom转换，和preact的dom转换做一些传递的改动，即可适配（详 **新增三**）\n\n### 改动一\n\n```typescript\nexport interface SharedConfig {\n  /**\n   * Configure what to use for jsx factory and fragment.\n   * @default\n   * {\n   *   factory: 'React.createElement',\n   *   fragment: 'React.Fragment'\n   * }\n   */\n  jsx?:\n    | 'vue'\n    | 'preact'\n    | 'react'\n    | {\n        factory?: string\n        fragment?: string\n      }\n}\n```\n\n### 改动二\n\n不论有没有引入，总是添加`import`语句；`resolveJsxOptions`被用于处理用户传入的`config.jsx`字段，转换为`{ jsxFactory: 'XXXXX', jsxFragment: 'XXXXX' }`，和之前处理jsx的配置不变，**好处就是方便用户使用**`preact` | `react`可以自动配置`jsxFactory`、`jsxFragment`。\n\n```typescript\nconst JsxPresets: Record<\n  string,\n  Pick<TransformOptions, 'jsxFactory' | 'jsxFragment'>\n> = {\n  vue: { jsxFactory: 'jsx', jsxFragment: 'Fragment' },\n  preact: { jsxFactory: 'h', jsxFragment: 'Fragment' },\n  react: {} // use esbuild default\n}\n\nexport function reoslveJsxOptions(options: SharedConfig['jsx'] = 'vue') {\n  if (typeof options === 'string') {\n    if (!(options in JsxPresets)) {\n      console.error(`[vite] unknown jsx preset: '${options}'.`)\n    }\n    return JsxPresets[options] || {}\n  } else if (options) {\n    return {\n      jsxFactory: options.factory,\n      jsxFragment: options.fragment\n    }\n  }\n}\n\n\nexport const transform = async (\n  src: string,\n  file: string,\n  options: TransformOptions = {},\n  jsxOption?: SharedConfig['jsx']\n) => {\n    let code = (result.js || '').replace(sourceMapRE, '')\n\n    // if transpiling (j|t)sx file, inject the imports for the jsx helper and\n    // Fragment.\n    if (file.endsWith('x')) {\n      if (!jsxOption || jsxOption === 'vue') {\n        code +=\n          `\\nimport { jsx } from '${vueJsxPublicPath}'` + // /vite/jsx createVnode处理器\n          `\\nimport { Fragment } from 'vue'`\n      }\n      if (jsxOption === 'preact') {\n        code += `\\nimport { h, Fragment } from 'preact'`\n      }\n    }\n\n    return {\n      code,\n      map: result.jsSourceMap\n    }\n  } catch (e) {\n  }\n}\n```\n\n### 新增三\n\npreact: `h(\"div\", null, \"Rendered from Preact JSX\", h(Test, {\n    count: 1337\n}))`\n\njsx：`jsx(\"div\", null, \"Rendered from Preact JSX\", jsx(Test, {\n    count: 1337\n}))`\n\ncreateVNode: `createVNode(\"div\", null, [\"Rendered from Preact JSX\", \n    createVNode(Test, {  count: 1337 }\n)])`\n\n> tag为字符串，转换为数组参数传入createVNode。\n>\n> tag为组件（就是个对象）转换为FunctionalComponent，返回数组vnode。\n\n#### 挖一下preact的h:\n\n3个参数外的参数，都是children，那就是说**转换为vue的只需要把包括参数3与大于参数3外的参数统一为数组**。\n\n```typescript\nexport function createElement(type, props, children) {\n\tlet normalizedProps = {},\n\t\ti;\n\tfor (i in props) {\n\t\tif (i !== 'key' && i !== 'ref') normalizedProps[i] = props[i];\n\t}\n\n    // 超出的参数 会组合成数组children中\n\tif (arguments.length > 3) {\n\t\tchildren = [children];\n\t\t// https://github.com/preactjs/preact/issues/1916\n\t\tfor (i = 3; i < arguments.length; i++) {\n\t\t\tchildren.push(arguments[i]);\n\t\t}\n\t}\n\tif (children != null) {\n\t\tnormalizedProps.children = children;\n\t}\n\n\t// If a Component VNode, check for and apply defaultProps\n\t// Note: type may be undefined in development, must never error here.\n\tif (typeof type == 'function' && type.defaultProps != null) {\n\t\tfor (i in type.defaultProps) {\n\t\t\tif (normalizedProps[i] === undefined) {\n\t\t\t\tnormalizedProps[i] = type.defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn createVNode(\n\t\ttype,\n\t\tnormalizedProps,\n\t\tprops && props.key,\n\t\tprops && props.ref,\n\t\tnull\n\t);\n}\n```\n\n#### 什么是FunctionalComponent呀？\n\n现在type为`function`，所以渲染标记为`0`，`FunctionalComponent`。\n\n```typescript\n# runtime-core vnode.ts\nconst shapeFlag = isString(type)\n    ? ShapeFlags.ELEMENT\n    : __FEATURE_SUSPENSE__ && isSuspense(type)\n      ? ShapeFlags.SUSPENSE\n      : isTeleport(type)\n        ? ShapeFlags.TELEPORT\n        : isObject(type) // 只要type是Obj 那么就是statefull_component\n          ? ShapeFlags.STATEFUL_COMPONENT\n          : isFunction(type)\n            ? ShapeFlags.FUNCTIONAL_COMPONENT\n            : 0\n```\n\n#### 为什么要转换呢？\n\n因为那是插槽，截取一段`vnode.ts`的`normalizeChildren`（影响用什么方式去渲染element）代码：\n\n```typescript\n# vnode.ts -> normalizeChildren\nif (isFunction(children)) {\n    children = { default: children, _ctx: currentRenderingInstance }\n    type = ShapeFlags.SLOTS_CHILDREN\n}\n```\n\n再看一下createVNode例子：\n\n![1](1.png)\n\n#### 本次新增的vueJsxCompat.ts代码\n\n```typescript\n# client/vueJsxCompat.ts\n\nimport { createVNode } from 'vue'\n\ndeclare const __DEV__: boolean\n\nif (__DEV__) {\n  console.log(\n    `[vue tip] You are using an non-optimized version of Vue 3 JSX, ` +\n      `which does not take advantage of Vue 3's runtime fast paths. An improved ` +\n      `JSX transform will be provided at a later stage.`\n  )\n}\n\n// 转换为插槽，(props.count) => \"Rendered from Preact TSX: count is \" + props.count\nexport function jsx(tag: any, props = null) {\n  const c =\n    arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null // 去除前两位\n  return createVNode(tag, props, typeof tag === 'string' ? c : () => c)\n}\n```\n\n\n\n# 342 - b0713fe 在服务端也输出hmr log\n\n客户端会输出hmr信息，但是在服务端没有，现在新增一个。\n\n```typescript\n# node/server/serverPluginHmr.ts\n\nif (needReload || needRerender || didUpdateStyle) {\n      let updateType = needReload ? `reload` : needRerender ? `template` : ``\n      if (didUpdateStyle) {\n        updateType += ` & style`\n      }\n      console.log(\n        chalk.green(`[vite:hmr] `) +\n          `${path.relative(root, file)} updated. (${updateType})`\n      )\n    }\n```\n\n\n\n# 343 - 2ac7469 importChain链将准确更新文件\n\n改动部分：\n\n`server/serverPluginHmr.ts`: 名称更换，加入`hmrDirtyFilesMap`，**记录文件改动时间戳需要更新哪些文件**（详 **改动一**）\n\n`server/serverPluginModulesRewrite.ts`: 从`node/utils/pathUtils.ts`迁移过来 `resolveImport`(id转换为publicPath)方法，并且通过`hmrDirtyFilesMap`传入`timestamp`获取`dirtyFiles`，被处理的`import`语句需要存在于`dirtyFiles`才可以添加`timestamp`。**不需要的再次载入的文件不会被载入，importChain得到强大的优化**。（详 **改动二**）\n\n### 改动一\n\n#### handleJSReload\n\n`vueImports` -> `vueBoundaries`（需要更新的SFC路径，就是谁引入了该文件）\n\n`jsImporters` -> `jsBoundaries`（需要更新的JS文件路径，使用了`hmr api`的文件或其`hmr.accept`的文件）\n\n`hmrDirtyFilesMap`: key: 文件更新的时间戳 <---> value: 改动文件的`publicPath` & 引入了`hmr api`的文件publicPath & 引入了该改动文件的**SFC文件publicPath**\n\n**`hmrDirtyFilesMap`在`server/serverPluginModulesRewrite.ts resolveImport`使用。**\n\n#### walkImportChain\n\n寻找import链的方法`walkImportChain`新增了`dirtyFiles`与`currentChain`参数，`currentChain`识别到`importer`类型为hot.accept(importee) | SFC，都会被添加进 `dirtyFiles.add(importer)`。\n\n**`currentChain`只定义，但未被使用。**\n\n> 再说亿遍，假如A.file引入了B.file，那么A.file叫importer，B.file叫importee。\n>\n> 引入过`hmr api`的文件（不要在SFC使用，无效果的，import被改写的时候就过滤了），他accept的文件都会被丢进一个key set对应关系。\n>\n> 还不懂请来微信dd我~\n\n### 改动二\n\n我们要知道参数`timestamp`相同的`get`请求，会自动使用缓存（浏览器）。\n\n```typescript\n# A.js\nimport C.js\nconsole.log(1)\n\n# B.vue\nimport A.js\n\n// 当A.js改变\n# A.js timestamp变更t=1\nimport C.js\nconsole.log(2)\n\n// dirtyFiles:['/A.js', '/B.vue']\n// hmrDirtyFilesMap:{ key: '1', value: dirtyFiles:['/A.js', '/B.vue'] }\n// 触发vue-reload 使得B.file被重新加载\n# 触发的语句，不是文件\nimport B.vue?t=1\n\n// resolveImport将要处理import语句\n// hmrDirtyFilesMap.get(1)\n// (dirtyFiles && dirtyFiles.has('/A.js')) || /\\.vue\\?type/.test('A.js')\n// (true && true) || false\n// 符合条件，添加timestamp\n# B.vue 被改写后\nimport A.js?t=1\n\n// resolveImport将要处理A.js?t=1的import语句\n// hmrDirtyFilesMap.get(1)\n// (dirtyFiles && dirtyFiles.has('/C.js')) || /\\.vue\\?type/.test('C.js')\n// (true && false) || false\n# A.js 被改写后\nimport C.js // 此时此刻C.js将会使用浏览器缓存\nconsole.log(2)\n```\n\n\n\n# 344 - 06e51cc build不要在index.html中重写外部引入的脚本 [#116](https://github.com/vitejs/vite/issues/166)\n\n改动部分：\n\n- `build/buildPluginHtml`: 编译html文件时，如果识别到src为外链，将不做处理。\n\n#### 改动前的行为\n\n![2](2.png)\n\n#### 改动后\n\n![3](3.png)\n\n\n\n# 345 - 02491a4(cva) fix [#111](https://github.com/vitejs/vite/issues/111) [#112](https://github.com/vitejs/vite/issues/122)\n\n可以使用`cva .`创建模板到当前文件中。\n\n如果创建文件夹失败，检测当前文件是否为空，非空则报错提示，空则在当前文件夹创建模板。\n\n\n\n# 346 - 97ae7c3 `__BASE__`值输出publicBasePath\n\n`publicBasePath`: 你的资源位置，如传入`/asd`，你的资源路径将全部加上`/asd`。\n\n![4](4.png)\n\n> 开发环境下的`__BASE__` 将被固定为`/`，构建环境下为`publicBasePath`\n\n> 是不是可以支持一下index.html的输出位置呢？因为资源和index.html经常不会在同一个位置。\n\n\n\n# 347 - 5346037 fix `client/vueJsxCompat.ts`\n\n如果传入的children是一个VNode，则转换为[VNode]，否则也被当slot。\n\n![5](5.png)\n\n> 要么是字符串，要么是数组，否则都不能走mountChildren\n\n\n\n# 348 - 185a9cd readme\n\nVite 是一个固执的 web 开发构建工具，它在开发过程中通过原生 ES 模块导入为您的代码提供服务，并将其与 [Rollup](https://rollupjs.org/)捆绑在一起用于生产。\n\nVite支持引入 `.ts` 文件 ，可在Vue SFC（开箱即用）中使用`<script lang=\"ts\">`。\n\nVite 只对`.ts` 文件执行转译，而**不** 执行类型检查。它假定类型检查由你的IDE 和构建过程负责（你可以在构建脚本中运行` tsc --noEmit`） \n\nVite 使用 [esbuild](https://github.com/evanw/esbuild) 将 TypeScript 转换为 JavaScript，这比通过使用原生js的`tsc` 快约 20 ~ 30 倍，并且 HMR 更新可以在 50 毫秒内反映在浏览器中。\n\n所有**静态**路径引用，包括绝对路径和以`/public`开头的路径，都应该基于你的工作目录结构。如果你在嵌套的公共路径下部署你的项目，只需指定`--base = /你的定义的路径/public/path/` ，所有资源路径都将会相应地重写。\n\n对于动态路径引用，有两种方式：\n\n- 你可以通过从 JavaScript 导入静态资源文件来获取解析的公共路径。例如，`import path from'./foo.png'` 会将其解析的公共路径作为字符串提供。\n- 如果你需要动态连接路径，你可以使用全局注入的 `__BASE__` 变量作为公共基本路径。\n\n### JSX\n\n还支持`.jsx` 和`.tsx` 文件。JSX 转译也通过`esbuild` 处理。请注意，目前没有对任何基于JSX 的自动HMR（只会刷新页面） 支持。\n\n默认的 JSX 配置在 Vue 3 中开箱即用：\n\n```jsx\nimport { createApp } from 'vue'\n\nfunction App() {\n  return <Child>{() => 'bar'}</Child>\n}\n\nfunction Child(_, { slots }) {\n  return <div onClick={console.log('hello')}>{slots.default()}</div>\n}\n\ncreateApp(App).mount('#app')\n```\n\nCurrently this is auto-importing a `jsx` compatible function that converts esbuild-produced JSX calls into Vue 3 compatible vnode calls, which is sub-optimal. Vue 3 will eventually provide a custom JSX transform that can take advantage of Vue 3's runtime fast paths.\n\n目前这是自动导入一个 `jsx` 兼容函数，该函数将 esbuild 生成的 JSX 调用转换为 Vue 3 兼容的 vnode，这是次优的。Vue 3 最终将提供一个自定义 JSX 转换，可以快速利用 Vue 3 的运行时路径。\n\n#### 关于 React / Preact 的 JSX\n\nThere are two other presets provided: `react` and `preact`. You can specify the preset by running Vite with `--jsx react` or `--jsx preact`. For the Preact preset, `h` is also auto injected so you don't need to manually import it.\n\n提供了两个预设：`react` 和`preact`。你可以通过使用`--jsx react` 或`--jsx preact` 运行Vite 来指定预设。对于Preact 预设，`h` 也是自动注入的，所以你不需要手动导入它。\n\n由于 React 不提供esm版，因此需要使用 [es-react](https://github.com/lukejacksonn/es-react)，或者使用 Snowpack 将 React 预先捆绑到 ES 模块中。最简单的方法让它运行是：\n\n```js\nimport { React, ReactDOM } from 'https://unpkg.com/es-react'\n\nReactDOM.render(<h1>Hello, what!</h1>, document.getElementById('app'))\n```\n\n如果你需要自定义JSX，也可以通过 CLI 中的 `--jsx-factory` 和 `--jsx-fragment` 标志或通过使用 API 中的 `jsx: {factory, fragment}` 来自定义 JSX。例如，你可以运行 `vite --jsx-factory = h` 来使用 `h` 进行 JSX 元素创建调用。\n\n## Config File\n\nYou can create a `vite.config.js` or `vite.config.ts` file in your project. Vite will automatically use it if one is found in the current working directory. You can also explicitly specify a config file via `vite --config my-config.js`.\n\nIn addition to options mapped from CLI flags, it also supports `alias`, `transforms`, and plugins (which is a subset of the config interface). For now, see [config.ts](https://github.com/vuejs/vite/blob/master/src/node/config.ts) for full details before more thorough documentation is available.\n\n你可以在你的项目中创建一个 `vite.config.js` 或 `vite.config.ts` 文件。如果能在当前工作目录中找到，Vite 会自动使用它。你也可以显式指定一个配置文件`--config my-config.js`。\n\n除了从 CLI 标志的选项之外，它还支持 `alias`、`transforms` 和插件（vite特色插件）。现在，请参阅 [config.ts](https://github.com/vuejs/vite/blob/master/src/node/config.ts)) 在更详尽的文档可用之前获取完整详细信息。\n\n> 可以使用alias定义`@`这些简化路径哦\n\n\n\n# 349 - fb95a11 changelog\n\n# [0.14.0](https://github.com/vuejs/vite/compare/v0.13.2...v0.14.0) (2020-05-10)\n\n### Bug Fixes\n\n- 不要在 index.html 中重写外部脚本(fix [#116](https://github.com/vuejs/vite/issues/116)) ([06e51cc](https://github.com/vuejs/vite/commit/06e51cc3ce2fbaeec3150394dac0b630b7601b78))\n- 修复加载 ts 配置 ([b85de93](https://github.com/Kingbultsea/vite-analysis/blob/352208e9ba4ea9555c19cc33f3af3921526df7e7/331-340/331-340.md#334---b85de93-%E4%BF%AE%E5%A4%8D%E5%8A%A0%E8%BD%BDts%E7%B1%BB%E5%9E%8B%E7%9A%84config))\n- 应该调用所有的插件transform ([#113](https://github.com/vuejs/vite/issues/113)) ([ed5b9e7](https://github.com/vuejs/vite/commit/ed5b9e7f51e906d3a42d056571c0d5091ed5cd4c))\n- **types:**修复`hmr hot.on`回调参数类型 ([a4524b4](https://github.com/vuejs/vite/commit/a4524b443ba6bfb53b78c053c27ac7ccb9f66749))\n\n\n\n# 350 - faf7dfc v0.14.0\n\nrelease v0.14.0\n"
  },
  {
    "path": "351-360/351-360.md",
    "content": "# 351 - cva v1.2.0\n\nrelase cva v1.2.0\n\n\n\n# 352 - cva 更新模板\n\n`fs`包更改为`fs-extra`支持`promise`。\n\n> 省略模板更新了什么\n\n\n\n# 353 - 3d16951 spinner在debug | test模式下不启用\n\nspinner会干扰控制台信息输出，在开启后会发现控制台的信息经常缺失。\n\n\n\n# 354 - 60e94e6 readme\n\n### 和 [Snowpack](https://www.snowpack.dev/)有什么不一样?\n\n- 专门针对 Vue，Vite 提供了内置的 HMR，而 Snowpack 只是在任何文件编辑时重新加载页面。由于这两种解决方案都依赖于原生 ES 导入，因此整页重新加载的网络瀑布实际上可能成为编辑 -> 反馈速度的瓶颈，HMR 允许你避免在开发时间内等待过长的页面reload。\n\n\n\n# 355 - e7b64f0 changelog \n\n## [0.14.1](https://github.com/vuejs/vite/compare/v0.14.0...v0.14.1) (2020-05-11)\n\n### Bug Fixes\n\n- 在记录写入之前停止spinner ([3d16951](https://github.com/vuejs/vite/commit/3d1695100a17502dcb49d074ed15627604cd03f0))\n\n### Features\n\n- **cva:**更新模板 ([8cd2354](https://github.com/vuejs/vite/commit/8cd235451f91b9a73c5419067af0c1bf7c992655))\n\n\n\n# 356 - 2e5585a release v0.14.1\n\nrelease v0.14.1\n\n\n\n# 357 - a968795 调整 issue template\n\n 调整 issue template\n\n\n\n# 358 - fd00853 cva v1.3.0\n\n更新vite依赖\n\nrelease cva v1.3.0\n\n\n\n# 359 - d58ae83 readme\n\n## Getting Started\n\n```bash\n$ npx create-vite-app <project-name>\n$ cd <project-name>\n$ npm install\n$ npm run dev\n```\n\n使用 Yarn:\n\n```bash\n$ yarn create vite-app <project-name>\n$ cd <project-name>\n$ yarn\n$ yarn dev\n```\n\n> 虽然 Vite 主要是为 Vue 3 设计的，但它实际上也可以支持其他框架。例如，尝试使用 `npx create-vite-app` 和 `--template react` 或 `--template preact`。\n\n\n\n# 360 - 0dafd33 chore cva路径 [#125](https://github.com/vitejs/vite/pull/125)\n\n内容路径需要更新一下。\n\n\n\n"
  },
  {
    "path": "361-370/361-370.md",
    "content": "# 362 - 0f106bd cva v1.3.1\n\nrelease cva v1.3.1\n\n\n\n# 363 - 0cacb17 增强编译error输出（格式）\n\n`server/serverPluginVue.ts` 增强编译error输出，报错位置输出换行（格式）\n\n\n\n# 364 - 995a827 依赖 bump vue\n\n更新vue包版本 ^3.0.0-beta11\n\n\n\n# 365 - 3974669 改进浏览器中的 hmr 失败消息\n\n改动部分：\n\n`client/client.ts`: 新增warnFailedFetch方法（详 **改动一**）\n\n### 改动一\n\n`vue-reload`会调用`__VUE_HMR_RUNTIME__.reload`，如果失败则提示：加载XXX失败，可能是由于语法错误或者引入的文件不存在（请查看上方的错误信息）\n\n```typescript\nfunction warnFailedFetch(err: Error, path: string | string[]) {\n  if (!err.message.match('fetch')) {\n    console.error(err)\n  }\n  console.error(\n    `[hmr] Failed to reload ${path}. ` +\n      `This could be due to syntax errors or importing non-existent ` +\n      `modules. (see errors above)`\n  )\n}\n```\n\n\n\n# 366 - 8558a6d cva 调整vue模板\n\n模板使用vue `Fragment`\n\n\n\n# 367 - 1cffde6 bump vue\n\n更新vue包，^3.0.0-beta12\n\n\n\n# 368 - de7b7f7 changelog\n\n## [0.14.2](https://github.com/vuejs/vite/compare/v0.14.1...v0.14.2) (2020-05-11)\n\n> 一个什么信息都没有的changelog\n\n\n\n# 369 - 800d0b2 v0.14.2\n\nrelease v0.14.2\n\n\n\n# 370 - c46007d cva v1.3.2\n\nrelease cva v1.3.2\n\n> vite更新 cva依赖的vite版本也需要更新\n\n"
  },
  {
    "path": "371-380/371-380.md",
    "content": "# 371 - 3e27277 chore git ignore explorations\n\n`.gitignore`添加`explorations`文件夹，我们想提交PR，fork `vite`后，可以在创建`explorations`文件夹在里面乱用代码。\n\n> 我这些调试的最方便了，不需要处理冲突，`sourcetree` merge UI线条也不会增多\n\n\n\n# 372 - b2377bf `isImportRequest`兼容safari\n\n我们可以在技术上使用更严格的检查，通过检查request 的`referer` 是不是compile-to-JS 源文件，但这不是在 Safari 中工作，因为 Safari 使用页面 URL 作为`referer`，即使对于 ES 模块imports。\n\n```typescript\nexport const isImportRequest = (ctx: Context): boolean => {\n  const dest = ctx.get('sec-fetch-dest')\n  if (dest && dest !== 'script') {\n    return false\n  }\n  return ctx.get('accept') === '*/*'\n}\n```\n\n`sec-fetch-dest`：表示请求的目的地\n\n`<link rel=\"stylesheet\" href=\"/style.css\">`显示`style`\n\n`import('/style.css')`显示`script`\n\n\n\n# 373 - 496b3fb wip(work in progress) service worker\n\n改动部分：\n\n- `src/node/server/serverPluginServiceWorker.ts`: （详 **新增一**）\n- `src/sw/serviceWorker.ts`: （详 **新增二**）\n- `src/node/server/serverPluginHmr.ts`：当触发`handleJSReload`，若检测到其处于import链，即传递`publicPath`，触发`bustSwCache`（在service-worker文件里交互的信息类型为`bust-cache`）。\n- `src/client/client.ts`: path参数，用于`bustSwCache`，`vue-rerender` | `vue-style-update` | `style-update` | `style-remove`会对path进行调整\n\n> [service-worker深度教程](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle)\n\n### serverPluginServiceWorker.ts 新增一\n\n#### 洋葱模型执行顺序（更新-2）\n\n```typescript\nimport { serveStaticPlugin } from './serverPluginServeStatic'\nimport { assetPathPlugin } from './serverPluginAssets'\nimport { cssPlugin } from './serverPluginCss'\nimport { jsonPlugin } from './serverPluginJson'\nimport { esbuildPlugin } from './serverPluginEsbuild'\nimport { vuePlugin } from './serverPluginVue'\nimport { moduleRewritePlugin } from './serverPluginModuleRewrite'\nimport { moduleResolvePlugin } from './serverPluginModuleResolve'\nimport { hmrPlugin, HMRWatcher } from './serverPluginHmr'\nimport { serviceWorkerPlugin } from './serverPluginServiceWorker.ts'\n\n# node/server.ts\nconst internalPlugins: Plugin[] = [\n  ...config.plugins,     // 洋葱模型的第一层  （自定）\n  serviceWorkerPlugin,   // 洋葱模型的第二层  （里层）\n  hmrPlugin,             // 洋葱模型的第三层  （里层）  \n  moduleRewritePlugin,   // 洋葱模型的第四层  （外层） --\n  moduleResolvePlugin,   // 洋葱模型的第五层  （里层）\n  vuePlugin,             // 洋葱模型的第六层  （内层）\n  esbuildPlugin,         // 洋葱模型的第七层  （外层） --\n  jsonPlugin,            // 洋葱模型的第八层  （外层） --\n  cssPlugin,             // 洋葱模型的第九层  （外层） --\n  assetPathPlugin        // 洋葱模型的第十层  （里层） \n  ServerTransformPlugin, // 洋葱模型的第十一层（外层） -- \n  serveStaticPlugin      // 洋葱模型的第十二层（里层）\n]\n```\n\n`vite`的`koa`插件，拦截`sw.js`发送`serviceWorker.js`文件（现在不分析todo要做的事情）\n\n```typescript\nimport fs from 'fs'\nimport path from 'path'\nimport { ServerPlugin } from '.'\n\n// 读取worker文件，__SERVER_TIMESTAMP__ 设置为vite服务开启时间\nlet swScript = fs\n  .readFileSync(path.resolve(__dirname, '../serviceWorker.js'), 'utf-8')\n  // inject server start time so the sw cache is invalidated\n  .replace(/__SERVER_TIMESTAMP__ =.*/, `__SERVER_TIMESTAMP__ = ${Date.now()}`)\n\n// TODO inject lockfile hash\n\n// TODO resolve module entry directly during rewrite so that we don't need the\n// redirect in module resolve plugin\n// 尤大想在这里改写@module，这样我们就不用跳转了\n\n\nexport const serviceWorkerPlugin: ServerPlugin = ({\n  app,\n  watcher,\n  resolver\n}) => {\n  if (process.env.DEBUG) {\n    // 在debug模式下允许控制台输出信息\n    swScript = swScript.replace(/\\/\\/ console.log/g, 'console.log')\n  }\n\n  // TODO watch lockfile hash\n\n  // const bustSwCache = (file: string) => {\n  //   // vue cache busting is handled in vue-specific client listeners\n  //   // so we can invalidate each blocks separately\n  //   if (!file.endsWith('.vue')) {\n  //     watcher.send({\n  //       type: 'sw-bust-cache',\n  //       timestamp: Date.now(),\n  //       path: resolver.fileToRequest(file)\n  //     })\n  //   }\n  // }\n\n  // watcher.on('change', bustSwCache)\n  // watcher.on('unlink', bustSwCache)\n\n  app.use(async (ctx, next) => {\n    if (ctx.path === '/sw.js') {\n      ctx.type = 'js'\n      ctx.status = 200\n      ctx.body = swScript\n      return\n    }\n    return next()\n  })\n}\n```\n\n### 新增二\n\n```typescript\n// 这两个是由服务器动态注入的，以便我们在服务器重新启动或用户锁定文件更改时使缓存无效。\nconst __SERVER_TIMESTAMP__ = 1\nconst __LOCKFILE_HASH__ = 'a'\n\nconst CACHE_NAME = `vite-cache-${__SERVER_TIMESTAMP__ + __LOCKFILE_HASH__}`\n\nconst sw = (self as any) as ServiceWorkerGlobalScope\n\nsw.addEventListener('install', () => {\n  // 跳过等待上一次ws 开发模式下必用 否则需要关闭所有选项卡 & 刷新页面\n  sw.skipWaiting()\n})\n\nsw.addEventListener('activate', (e) => {\n  // 首次加载页面可以控制作用域内的请求（对时间敏感）\n  sw.clients.claim()\n    \n  // 删除没有匹配到CACHE_NAME的缓存\n  e.waitUntil(\n    (async () => {\n      const keys = await caches.keys()\n      for (const key of keys) {\n        if (key !== CACHE_NAME) {\n          await caches.delete(key)\n        }\n      }\n    })()\n  )\n})\n\nsw.addEventListener('message', async (e) => {\n  // 删除当前CACHE_NAME下的某缓存\n  if (e.data.type === 'bust-cache') {\n    const cache = await caches.open(CACHE_NAME)\n    // console.log(`busted cache for ${e.data.path}`)\n    cache.delete(e.data.path)\n  }\n})\n\nconst cacheableRequestRE = /^\\/@modules\\/|\\.vue($|\\?)|\\.(t|j)sx?$|\\.css$/\nconst hmrRequestRE = /(&|\\?)t=\\d+/\n\n// 请求\nsw.addEventListener('fetch', (e) => {\n  const url = new URL(e.request.url)\n  // @modules .vue .jsx .tsx .css使用缓存，但是拥有t参数的不使用缓存\n  if (\n    cacheableRequestRE.test(url.pathname) &&\n    // no need to cache hmr update requests\n    !url.search.match(hmrRequestRE)\n  ) {\n    e.respondWith(tryCache(e.request))\n  }\n})\n\nasync function tryCache(req: Request) {\n  const cached = await caches.match(req)\n  // 拥有缓存则返回缓存res\n  if (cached) {\n    // console.log(`serving ${req.url} from cache`)\n    return cached\n  } else {  \n    // console.log(`fetching`, req)\n    const res = await fetch(req)\n    // console.log(`got res:`, res)\n    // 无效请求 https://developer.mozilla.org/zh-CN/docs/Web/API/Response/type\n    if (!res || res.status !== 200 || res.type !== 'basic') {\n      // console.log(`not caching ${req.url}`)\n      return res\n    }\n      \n    // 缓存  \n    // console.log(`caching ${req.url}`)\n    const cache = await caches.open(CACHE_NAME)\n    cache.put(req, res.clone())\n    return res\n  }\n}\n```\n\n> sw事件`bust-cache`，传入`path`，可删除CACHE_NAME的res缓存。\n>\n> 现在一切的缓存|import链，都是publicPath，publicPath是唯一的。\n\n\n\n# 374 - e18f21a 调整css，不需要raw参数\n\n改动部分：\n\n- `serverPluginModuleRewrite.ts`: 非`.jsx` | `.tsx` | `.vue`后缀的import语句，加入参数`import`作为标识。\n- `node/pathUtils.ts`: 不再检测请求头，通过参数`import`来区别这是不是一个来自脚本的请求。\n- `serverPluginCss.ts`：识别到参数带有`raw`，将直接返回存文件内容（交给静态资源获取的插件处理）。\n\n\n\n# 375 - b4b84e3 sw cache busting\n\n改动部分：\n\n- `src/client/client.ts`: `sw`文件改动，询问是否重新加载页面;新增changSrcPath，用于调用bustSwCache删除缓存；`vue-style-update` 去除参数`t`。（详 **改动一**）\n- `src/server/serverPluginHmr.ts`: `HMRPayload`新增参数`changeSrcPath`，`vue-reload` & `js-update`均传递`changeSrcPath`（详 **改动二**）\n- `src/server/serverPluginModuleResolve.ts`: 去除跳转功能。\n- `src/server/serverPluginModuleRewrite.ts`: `serverPluginModuleResolve.ts`的跳转路径，被迁移到这里的`resolveImport`，即`/@modules/${resolveNodeModuleEntry(root, id) || id}`。\n- `serverPluginServiceWorker.ts`: `watch.on('unlink')`触发`sw-bust-cache`事件（文件删除会触发），新增`__PROJECT_ROOT__ = root`。\n\n> Cache Busting，是指通过一定技术手段, 强行使得浏览器端的缓存失效, 使得浏览器获取资源的最新版本。\n>\n> tips: 使用者只能通过`resolver.alias`来影响对模块的路径改写。\n\n### 改动一\n\n触发`bustSwCache`方式：\n\n1. `sw-bust-cache`事件，传递`path`\n2. 任意事件，传递`changeSrcPath`\n3. `vue-rerender`，自动触发，`${path}?type=template`\n4. `vue-style-update`，自动触发，``${path}?type=style&index=${index}``\n5. `full-reload`事件，传递`path`\n\n**为什么`serviceWorker`变动，需要询问是否reload页面？**\n\n因为`serviceWorker`的更新不能确保第一时间能拦截所有请求，(sw安装成功)重新加载页面才能保证。\n\n**为什么去除参数`t`，这样不是浏览器一直都缓存了吗？**\n\n上了`serviceWorker`后，就不需要参数`t`了。\n\n### 改动二\n\n传递`changeSrcPath`的事件：\n\n1. `vue-reload`事件，`changeSrcPath = publicPath`\n2. `vue-js`事件，`changeSrcPath = publicPath`\n\n\n\n# 376 - 3dc39fa commnents\n\n`client.ts`添加注释，vite经常更新，sw也会更新，所以注释提醒用户是否需要重新reload页面。\n\n`sw-bust-cache`: 这只会在文件被删除的时候触发。\n\n`full-reload`: 传递的path也派上用场了（符合`hasDeadEnd`，`path`为文件改动的`publicPath`）\n\n\n\n# 377 - ee6a03d 去除`sw-bust-cache`事件，根据`-sw`决定是否使用缓存\n\n改动部分：\n\n- `client/client.ts`: 去除`sw-bust-cache`事件，`full-reload`不会触发`bustSwCache`，现在改为`path`不等于`changeSrcPath`触发`bustSwCache(path)`。\n- `node/cli.ts`: 可以通过`-sw`来开启`service-wroker`，类型`[boolean | deps-only]`，默认`true`\n- 其余部分根据`-sw`配置，选择性进行缓存\n- `../client.js`将不再缓存（不知道为什么，我觉得是暂时性停止缓存，后续还是会根据`-sw`加上去的）\n\n1. 配置`-sw`为`true`: 开启完全功能的`service-worker`\n2. 配置`-sw`为`false`: `service-worker`的`fetch`将不做任何操作，服务器会判断304缓存，开启`koa-conditional-get` & `koa-etag`\n3. 配置`-sw`为`deps-only`: 继承点**2**，`sw`会在匹配到非`/@modules/`的`publicPath`请求，不使用`service-worker`\n\n\n\n# 378 - 59d0103 测试sw\n\n页面刷新后，测试hmr是否正常工作。\n\n\n\n# 379 - 3408b97 `koa-etag`重新改为默认调用，即使开启了`-sw = true`，也要设置`etag`\n\n改动部分：\n\n- `server/serverPluginServiceWorker.ts`: `__SERVER_TIMESTAMP__ = ${config.serviceWorker ? Date.now() : '0'}`，如果`-sw`为`false`，则`__SERVER_TIMESTAMP__ === '0'`（详 **改动一**）\n- `server/serverPluginServerStatic.ts`: `koa-etag`回到当初改动，默认调用\n- `server/serverPluginVue.ts` & `node/utils/fsUtils.ts cachedRead `:  不管什么情况，正常设置`etag` `header`\n\n> cachedRead 设置304，依旧会设置body（serverPluginStatic）\n>\n\n### 改动一\n\n`__SERVER_TIMESTAMP__`的变动，可以让vite服务重启后使得`sw.js`内容有所改动，可以触发重新安装sw。\n\n### BUG\n\nindex.html与src注册的importMap关系，在windows下不正确，需要使用`posix`。\n\n\n\n# 380 - 3bb1324 lockfile hash 新增lockFile文件的hash值给`sw`\n\n改动部分：\n\n`server/serverPluginServiceWorker.ts`: 新增`__LOCKFILE_HASH__`，变量更名，新增方法`getLockfileHash`（详 **改动一**）\n\n### 改动一\n\n更名： `__SERVER_TIMESTAMP__`  -> `__SERVER_ID__`。\n\n`__LOCKFILE_HASH__ = ${JSON.stringify(getLockfileHash(root))}`\n\n```typescript\n// lockfile可能的名称\nconst lockfileFormats = [\n  'package-lock.json',\n  'yarn.lock',\n  'pnpm-lock.yaml',\n  'package.json'\n]\n\nfunction getLockfileHash(root: string): string {\n  for (const format of lockfileFormats) {\n    const fullPath = path.join(root, format)\n    \n    // 寻找到则使用'crypto'包的createHash创建为base64数据格式的hash\n    if (fs.existsSync(fullPath)) {\n      const content = fs.readFileSync(fullPath, 'utf-8')\n      return createHash('sha1').update(content).digest('base64')\n    }\n  }\n  return ``\n}\n```\n\n"
  },
  {
    "path": "381-390/381-390.md",
    "content": "# 381 - e8e4a4b remove todo\n\n去除todo: 是否使用文件内容hash 或者 lastModified来代替timestamp?\n\n![1](1.png)\n\n`sw`缓存有两种类型：\n\n1. `const USER_CACHE_NAME = vite-cache-${__PROJECT_ROOT__}-${__SERVER_ID__}`，模块的缓存，即匹配`@modules`\n\n2. `const DEPS_CACHE_NAME = vite-cache-${__PROJECT_ROOT__}-${__LOCKFILE_HASH__}`，用户文件缓存，即匹配`/(&|\\?)t=\\d+/`，参数t\n\n每次服务启动，sw更新，都会删除用户缓存。\n\n而模块缓存，根据`lockfile`的hash变动来决定（如你的`yarn.lock` | `package-lock.json`）。\n\n\n\n# 382 - b5871eb fix 改写import id同样也需要改写为webmodules\n\n改动部分：\n\n- `server/serverPluginModuleRewrite.ts`: 改写为`@modules/`的`resolveImport`，需要添加对`webmodules`的路径改写支持（详 修复一）\n\n### 修复一\n\n优先判断`webmodules`。\n\n```typescript\nreturn `/@modules/${\n      resolveWebModule(root, id) || resolveNodeModuleEntry(root, id) || id\n}`\n```\n\n\n\n# 383 - 0e7ea5a 选项错误提示 `deps-only`的小修改\n\n改动部分：\n\n- `server/serverPluginServiceWorker.ts`: 用户输入的选项`enabled`如果为字符串，但是不为`deps-only`，则输出错误警告，此时将改为`true`；`config.serviceWorker === true`才会把`__SERVER_ID__`设置为服务启动时间。\n\n```typescript\n`const __SERVER_ID__ = ${\n        // only inject if caching user files. When caching deps only, only\n        // the lockfile change invalidates the cache.\n        config.serviceWorker === true ? Date.now() : '0'\n      }`\n```\n\n> deps-only是来缓存模块的，`__SERVER_ID__`不应该去改动它（虽然没什么大碍，服务重启一个tip reload）。\n\n\n\n# 384 - 82098a8 sw去除deps-only\n\n改动部分：\n\n- `client/client.ts`: `__SW_ENABLED__`为真值 或 已经存在sw服务 才注册sw服务（存在也要注册一遍，可以删除旧的cache）；新增`env.d.ts`（详 **改动一**）\n- `-sw`去除`deps-only`，现在从默认为`true`更改为默认`false`\n- `server/serverPluginModuleRewrite.ts`: `window.__SW_ENABLED__ = ${!!config.serviceWorker}`现在被设置为全局字段\n- `server/serverPluginServeStatic.ts`: `-sw`为`false`，即使用`koa-conditional-get`（实际没改动，原来为`!==true`）\n- `server/serverPluginServiceWorker.ts` & `serviceWorker.ts`：去除`deps-only`逻辑\n\n> 完全去除`deps-only`，不区分什么用户缓存|模块缓存了。\n>\n> `sw`缓存以`hashlocak` & `timestamp`为准。\n\n### 改动一\n\n命名全局变量类型。\n\n```typescript\n# env.d.ts\n\ndeclare const __DEV__: boolean\ndeclare const __BASE__: string\ndeclare const __SW_ENABLED__: boolean\n```\n\n\n\n# 385 - 617f9f0 测试 async组件显示`performance.now()`\n\n[performance.now()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now)\n\n返回值表示为从[time origin](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#the_time_origin)之后到当前调用时经过的时间\n\n\n\n# 386 - fccc261 -sw测试\n\n添加`appveyor.yml` & `circleci`的serviceWorker测试。\n\n\n\n# 387 - 893ae7b windows ci\n\n区分windows测试，设置`crocess-env`。\n\n\n\n# 388 - ce41994 修复web_modules改写错误\n\n因为`serverPluginModuleResolve.ts`会进行resolve处理，所以返回id即可\n\n```typescript\nconst isWebModule = !!resolveWebModule(root, id)\nreturn `/@modules/${\n    isWebModule ? id : resolveNodeModuleEntry(root, id) || id\n}`\n```\n\n\n\n# 389 - 2efe9b3 serviceWorker可用才注册sw\n\n检测`navigator.serviceWorker`是否存在\n\n\n\n# 390 - a44bd76 简化ci的脚本\n\n可能尤大觉得crocess-env只能在windows用吧，又恢复回去了，现在ci测试脚本统一使用`test` `test-sw`\n\n```json\n\"test\": \"jest --clearCache && jest --runInBand --forceExit\",\n\"test-sw\": \"cross-env USE_SW=1 yarn test\",\n```\n\n"
  },
  {
    "path": "391-400/391-400.md",
    "content": "# 391 - f9b267f cva 使用@pika/react & alias\n\n![1](1.png)\n\n\n\n# 392 - c8a1ffd 服务启动后的每个首次请求不返回304\n\n改动部分：\n\n- `node/server/serverPluginServeStatic.ts`: 去除`koa-conditional-get`，使用`ctx.fresh`（详 **改动一**）\n- `node/server/serverPluginVue.ts`: `seenUrls`检测是否首个请求，是则返回200\n- `node/utils/fsUtils.ts`: `seenUrls`检测是否首个请求，是则返回200\n\n**为什么不要第一个缓存呢？**\n\n因为`moduleRewritePlugin.ts`检测到304，就不做改写了，比如SFC文件返回304，就不能够改写`import`，也就没有importChain。\n\n> 虽然`serverPluginVue.ts`没有调用next()，但是记住`serverPluginRewrite.ts`先被use的，是他调用了`await next()`，让SFC处理后，再回头改写`import`\n\n### 改动一\n\nseenUrls用户储存ctx.url，用来判断该请求是否为新的请求。\n\n```typescript\nif (!config.serviceWorker) {\n  app.use(async (ctx, next) => {\n    await next()\n    // the first request to the server should never 304\n    if (seenUrls.has(ctx.url) && ctx.fresh) {\n      ctx.status = 304\n    }\n    seenUrls.add(ctx.url)\n  })\n}\napp.use(require('koa-etag')())\n```\n\n### request.fresh\n\n检查请求缓存是否“新鲜”，也就是内容没有改变。此方法用于 `If-None-Match` / `ETag`, 和 `If-Modified-Since` 和 `Last-Modified` 之间的缓存协商。 在设置一个或多个这些响应头后应该引用它。\n\n```js\n// 新鲜度检查需要状态20x或304\nctx.status = 200;\nctx.set('ETag', '123');\n\n// 缓存是好的\nif (ctx.fresh) {\n  ctx.status = 304;\n  return;\n}\n\n// 缓存是陈旧的\n// 获取新数据\nctx.body = await db.find('something');\n```\n\n\n\n# 393 - f3a45f3 changelog\n\n## [0.14.3](https://github.com/vuejs/vite/compare/v0.14.2...v0.14.3) (2020-05-12)\n\n### Bug Fixes\n\n- 服务器启动时的第一个请求不应为304 ([c8a1ffd](https://github.com/vuejs/vite/commit/c8a1ffd71db0916cd6386130e3eb170fa09c31d2))\n- web modules resolve，返回id即可，因为`serverPluginModuleResolve.ts`会进行resolve处理 ([ce41994](https://github.com/vuejs/vite/commit/ce41994ee4e395bb304191b5d9a26f0f32d3b47a))\n- 让`isImportRequest`也能在Safari中检测（后面改写为直接检测`ctx.query.import`参数，在`serverPluginModuleRewrite.ts`中会加入`import`参数） ([b2377bf](https://github.com/vuejs/vite/commit/b2377bf3b6b14ed972327930644fe6937fa814dd))\n- sw 可用才进行注册 ([2efe9b3](https://github.com/vuejs/vite/commit/2efe9b3215f04e751d19cd50169bddf4250d114d))\n- 对于改写import路径，也需要处理web_modules ([b5871eb](https://github.com/vuejs/vite/commit/b5871eba505e5a109b8b8ae07d6f8a70c6d970eb))\n\n### Features\n\n- **cva:** @pika/react + alias ([f9b267f](https://github.com/vuejs/vite/commit/f9b267fbe3f6e8cc11a3e6855f7775aeb863b0f8))\n- **sw:** lockfile hash ([3bb1324](https://github.com/vuejs/vite/commit/3bb13240d9d6c3ef84020cd69b2e60835f206f8f))\n- service worker 缓存([ee6a03d](https://github.com/vuejs/vite/commit/ee6a03d3497433150c13fc9370b17daaa43e1e1d))\n\n\n\n# 394 - 120cd78 v0.14.3\n\nrelease v0.14.3\n\n\n\n# 395 - 8a1fbfe cva v1.4.0\n\nrelease create-vite-app v1.4.0\n\n\n\n# 396 - f558a88 [#133](https://github.com/vitejs/vite/pull/133) cleanUrl 使用ctx.path\n\n将`cleanUrl`替换为 `ctx.path`\n\n\n\n# 397 - 140751f wip 新增optimize命令\n\n改动部分：\n\n- 新增`@rollup/plugin-commonjs: ^11.1.0`，用来寻找包入口。\n- 新增`src/node/optimizer.ts`（详 **新增二**）\n\n### 新增二\n\n`dependencies`的包，转换为es5。（目前有BUG，留~）\n\n```typescript\nimport fs from 'fs-extra'\nimport path from 'path'\nimport { createHash } from 'crypto'\nimport { ResolvedConfig } from './config'\nimport type Rollup from 'rollup'\nimport { supportedExts } from './resolver'\n\nexport interface OptimizeOptions extends ResolvedConfig {\n  force?: boolean\n}\n\nexport async function optimize(config: OptimizeOptions) {\n  // scan lockfile\n  const root = config.root || process.cwd()\n  const cacheDir = path.join(root, `node_modules`, `.vite`)\n  console.log(cacheDir)\n  const hashPath = path.join(cacheDir, 'hash')\n  const depHash = getDepHash(root, config.__path)\n\n  if (!config.force) {\n    let prevhash\n    try {\n      prevhash = await fs.readFile(hashPath, 'utf-8')\n    } catch (e) {}\n    // hash is consistent, no need to re-bundle\n    if (prevhash === depHash) {\n      console.log('hash is consistent. skipping.')\n      return\n    }\n  }\n\n  await fs.ensureDir(cacheDir)\n  await fs.writeFile(hashPath, depHash)\n\n  const pkg = lookupFile(root, [`package.json`])\n  if (!pkg) {\n    console.log(`package.json not found. skipping.`)\n    return\n  }\n\n  const deps = JSON.parse(pkg).dependencies || {}\n  const depKeys = Object.keys(deps)\n  if (!depKeys.length) {\n    console.log(`no dependencies listed in package.json. skipping.`)\n    return\n  }\n\n  console.log(`optimizing dependencies...`)\n  const rollup = require('rollup') as typeof Rollup\n\n  console.log(depKeys)\n\n  await rollup.rollup({\n    input: depKeys,\n    plugins: [\n      require('@rollup/plugin-node-resolve')({\n        rootDir: root,\n        extensions: supportedExts\n      }),\n      require('@rollup/plugin-commonjs')({\n        sourceMap: false\n      })\n    ]\n  })\n}\n\nconst lockfileFormats = [\n  'package-lock.json',\n  'yarn.lock',\n  'pnpm-lock.yaml',\n  'package.json'\n]\n\nlet cachedHash: string | undefined\n\nexport function getDepHash(\n  root: string,\n  configPath: string | undefined\n): string {\n  if (cachedHash) {\n    return cachedHash\n  }\n  let content = lookupFile(root, lockfileFormats) || ''\n  // also take config into account\n  if (configPath) {\n    content += fs.readFileSync(configPath, 'utf-8')\n  }\n  return createHash('sha1').update(content).digest('base64')\n}\n\nfunction lookupFile(dir: string, formats: string[]): string | undefined {\n  for (const format of formats) {\n    const fullPath = path.join(dir, format)\n    if (fs.existsSync(fullPath)) {\n      return fs.readFileSync(fullPath, 'utf-8')\n    }\n  }\n  const parentDir = path.dirname(dir)\n  if (parentDir !== dir) {\n    return lookupFile(parentDir, formats)\n  }\n}\n```\n\n\n\n# 398 - fa52279 wip vite optimize\n\n改动部分：\n\n`server/serverPluginModuleResolve.ts`: 新增`resolveOptimizedModule` & `resolveBareModule`方法，处理`vite optimized modules`（详 **新增一**）\n\n### 新增一\n\n虽然397不能使用，但是可以看出: `vite optimized`转换`dependcies`的所有包为`es5`，vite检测到这类模块，则优先利用。\n\n`resolveBareModule`提供给`serverPluginRewrite.ts`判断模块类型使用。\n\n> 把所有文件给打进一个文件，可以减少网络请求。\n\n```typescript\nexport function resolveBareModule(root: string, id: string) {\n  const optimized = resolveOptimizedModule(root, id)\n  if (optimized) {\n    return id + '.js'\n  }\n  const web = resolveWebModule(root, id)\n  if (web) {\n    return id + '.js'\n  }\n  const nodeEntry = resolveNodeModuleEntry(root, id)\n  if (nodeEntry) {\n    return nodeEntry\n  }\n  return id\n}\n\n\nconst viteOptimizedMap = new Map()\n\nexport function resolveOptimizedModule(\n  root: string,\n  id: string\n): string | undefined {\n  const cached = viteOptimizedMap.get(id)\n  if (cached) {\n    return cached\n  }\n\n  if (!id.endsWith('.js')) id += '.js'\n  const file = path.join(root, `node_modules`, `.vite`, id)\n  if (fs.existsSync(file)) {\n    viteOptimizedMap.set(id, file)\n    return file\n  }\n}\n```\n\n\n\n# 399 - 405f685 代码整理\n\n改动部分：\n\n- `serverPluginModuleResolve.ts`:  封装`index.html`代码改写逻辑\n- `serverPluginModuleRewrite.ts`: 整理代码，名称更改，增强错误输出（详 **改动二**）\n\n### 改动二\n\n如果模块无法处理，则输出`referer`的路径（引入了这个模块的文件路径）。\n\n```typescript\nconst importer = new URL(ctx.get('referer')).pathname\n    console.error(\n      chalk.red(\n        `[vite] Failed to resolve module import \"${id}\". ` +\n          `(imported by ${importer})`\n      )\n    )\n```\n\n\n\n# 400 - e62473a 获取文件后缀使用`path.extname`\n\n把正则`/\\.\\w+$/`更改为`path.extname`\n\n"
  },
  {
    "path": "401-410/401-410.md",
    "content": "# 401 - 7f5e459 代码整理\n\n 代码整理\n\n\n\n# 402 - 02753b7 添加`.mjs`拓展\n\n引入模块尝试，在寻找不了`package.json`的情况下，拓展文件后缀。\n\n如`import lodash from 'lodash-es/lodash'` -> `'@modules/lodash-es/lodash.js'`\n\n\n\n# 403 - 4f2953e 修复windows下模块的入口路径处理\n\n`path.join(id, '/', pkg.module || pkg.main || 'index.js')`\n\n更改为：`id + '/' + (pkg.module || pkg.main || 'index.js')`\n\n以前返回：`lodash-es\\lodash.js`\n\n现在返回：`lodash-es/lodash.js`\n\n改写到浏览器，为windows路径，传递到服务器寻找资源就错误了。\n\n```js\npath.join('/foo', 'bar', 'baz/asdf', 'quux', '..');\n// Returns: '/foo/bar/baz/asdf'\n\npath.join('foo', {}, 'bar');\n// Throws 'TypeError: Path must be a string. Received {}'\n```\n\n> id就是import from 'id'，pkg.module为package.json module pkg.main 为package.json main\n\n\n\n# 404 - c243d09 cva & 构建下alias可以在其他plugins中使用\n\n改动部分：\n\n- `cva`命令行：`build: \"vite build src --jsx react --outDir dist\"`\n- `build/buildPluginResolve.ts`: 在resolveId钩子使用resolve（详 **改动二**）\n\n[rollup this.resolve](https://rollup.docschina.org/guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null): 重新运行钩子，但是跳过当前。\n\ntips: 你可以不把skip关掉，你就知道这个有什么用。\n\n### 改动二\n\n`resolveId`内调用resolve，等于重新走一次流程了，加`skipSelf`跳过自身钩子。\n\n为了让`id = resolver.alias(id) || id`起作用，所以调用`resolve`重新执行流程（`skpiSelf`跳过自身，不然无限循环），如果别的钩子进行处理（不存在，这里添加await也可以），就默返回被`alias`处理过的结果。\n\n```typescript\nasync resolveId(id, importer) {\n   // fallback to node-resolve\n   const resolved = this.resolve(id, importer, { skipSelf: true })\n   return resolved || { id }\n}\n```\n\nhttps://github.com/rollup/rollup/pull/2844\n\n> 对应模块设置moduleSideEffects后，其他引入者只关注你的模块变量是否被引入的部分，副作用不管。\n\n![1](1.png)\n\n> 之前我对tree-shaking有误解，以为require也可以做到很好的tree-shaking（反正流程是可以知道引入了什么），实际是忘了动态引入，根本分析不了。esm就可以很好知道谁引入了，谁没引入了，sideEffect false可以完全保证tree-shaking（要遵守没有副作用的代码原则）\n\n\n\n# 405 - b05808d changlog\n\n## [0.14.4](https://github.com/vuejs/vite/compare/v0.14.3...v0.14.4) (2020-05-13)\n\n### Bug Fixes\n\n- cva对react构建命令更改 + 构建下alias可以在其他plugins中使用 ([c243d09](https://github.com/vuejs/vite/commit/c243d09dbb7cbc7aaf5c79e2e2ea3be899d37933))\n- 修复windows下模块改写错误 ([4f2953e](https://github.com/vuejs/vite/commit/4f2953e429718c28ec4f1a8e6559d7c75630e70b))\n- 支持.mjs拓展 &模块路径寻找中尝试添加拓展名 ([02753b7](https://github.com/vuejs/vite/commit/02753b7fda300bd15b7fa61d5e9ed2cce1a6ac4f)), closes [#127](https://github.com/vuejs/vite/issues/127)\n- **history-fallback:** 添加`.`正确重定向网址 ([7f5e459](https://github.com/vuejs/vite/commit/7f5e4596a4e7254cc5f173fbf5261f3f47c926a9)), closes [#130](https://github.com/vuejs/vite/issues/130)\n- 使用ctx.path代替cleanUrl ([#133](https://github.com/vuejs/vite/issues/133)) ([f558a88](https://github.com/vuejs/vite/commit/f558a880a3aa04f6024ff05f25924568a94a9b54))\n\n### Features\n\n- 改进模块解析([405f685](https://github.com/vuejs/vite/commit/405f685f7b0772881f5bd296b136296e94e35085))\n\n\n\n# 406 - 816f3e5 v0.14.4\n\nrelease v0.14.4\n\n\n\n# 407 - c0c1991 cva v1.4.1\n\nrelease cva v1.4.1\n\n\n\n# 408 - 976b8a2 自动卸载sw\n\n改动部分：\n\n- `client/client.ts`: 在sw被禁止的情况下，如果发现有sw，则把它卸载掉\n- `serverPluginHmr.ts`: `__SW_ENABLED__ = !!config.serviceWorker`，提供给`client.ts`使用\n- `sw/serviceWorker.ts`: 对`client.ts`不进行缓存\n\n\n\n# 409 - e6bfd20 cva 原本在命令行的flag现移动在config中配置jsx\n\n![2](2.png)\n\n\n\n# 410 - 49a44b6 服务启动即调用`optimizeDeps` & 去除`web_modules` & 支持命名导入cjs & 去除`windows.__SW_ENABLED__`\n\n改动部分：\n\n- `node/resolver.ts`: 新增`resolveBareModule` & `resolveOptimizedModule` ；迁移 `resolveNodeModule`到这里（详 **改动一**）\n- `build/buildPluginResolve.ts`: `resolveId`中去除对`webModulePath`的路径处理\n- `build/index.ts`: 封装部分plugin为`createBaseRollupPlugins`，提供`optimizeDeps` rollup构建使用；使用`@rollup/plugin-commonjs`支持命名导入cjs（详 **改动三**）\n- `node/depOptimizer.ts`: 把`node/optimizer.ts`重新命名为`/node/depOptimizer.ts`\n- `node/server/serverPluginModuleRewrite.ts`: `__SW_ENABLED__`在`clint.ts`替换即可，不需要暴露到`windows`\n\n### 改动一\n\n#### resolveOptimizedModule\n\n比如现在有A包，经过路径处理会变成`/@modules/depOptimizer/A.js`\n\n> 我比较好奇的是为什么不直接使用resolveEx拓展后缀？而且在这里拓展成A.js是错误吧。\n>\n> 目前windwos有BUG，这里反向推理可以知道optimizeDeps把dependencies的包给打成一个文件入口了。\n\n```typescript\nimport { OPTIMIZE_CACHE_DIR } from './depOptimizer'\n// `node_modules/.vite_opt_cache`\n\nconst viteOptimizedMap = new Map()\n\nexport function resolveOptimizedModule(\n  root: string,\n  id: string\n): string | undefined {\n  // 返回缓存 \n  const cached = viteOptimizedMap.get(id)\n  if (cached) {\n    return cached\n  }\n\n  if (!id.endsWith('.js')) id += '.js' // 尝试添加.js后缀\n  const file = path.join(root, OPTIMIZE_CACHE_DIR, id) // 合并路径\n  if (fs.existsSync(file)) { // 存在则设置文件路径\n    viteOptimizedMap.set(id, file)\n    return file\n  }\n}\n```\n\n#### resolveNodeModule\n\n寻找包入口功能，从`src/node/server/serverPluginModuleResolve.ts`迁移过来。\n\n```typescript\nexport function resolveNodeModule(\n  root: string,\n  id: string\n): string | undefined {\n  const cached = nodeModulesMap.get(id)\n  if (cached) {\n    return cached\n  }\n\n  let pkgPath\n  try {\n    // see if the id is a valid package name\n    pkgPath = resolveFrom(root, `${id}/package.json`)\n  } catch (e) {}\n\n  if (pkgPath) {\n    // if yes, this is a entry import. resolve entry file\n    const pkg = require(pkgPath)\n    const entryPoint = id + '/' + (pkg.module || pkg.main || 'index.js')\n    debug(`(node_module entry) ${id} -> ${entryPoint}`)\n    nodeModulesMap.set(id, entryPoint)\n    return entryPoint\n  } else {\n    // 可能是完整import路径\n    try {\n      return resolveFrom(root, id)\n    } catch (e) {}\n\n    // 尝试所有拓展名\n    if (!path.extname(id)) {\n      for (const ext of supportedExts) {\n        try {\n          return resolveFrom(root, id + ext)\n        } catch (e) {}\n      }\n    }\n  }\n}\n```\n\n### 改动三\n\n`createBaseRollupPlugins`包括的插件有：\n\n1. user plugins 用户定义的插件\n2. vite:resolve 处理vite Id的插件（`resolver.alias` | `vue or @vue/包路径`  | `optimizedModule`  ）\n3. vite:esbuild （转换ts + 代码压缩）\n4. rollup-plugin-vue 转换SFC\n5. @rollup/plugin-json 转换JSON\n6. user transforms （本质rollup transform 转换代码）\n7. @rollup/plugin-commonjs 把cjs模块转换为esm\n\n##### @rollup/plugin-commonjs\n\n```typescript\nrequire('@rollup/plugin-commonjs')({\n    extensions: ['.js', '.cjs'], // 对于无扩展import,按指定的顺序搜索 .js 以外的扩展名。需要先确保非 JavaScript 文件由另一个插件先转译处理\n    namedExports: knownNamedExports\n})\n```\n\n##### 自定义命名导出\n\n插件尝试创建named exports\n\n```typescript\n// importer.js\nimport { named } from './exporter.js';\n\n// exporter.js\nmodule.exports = { named: 42 }; // or `exports.named = 42;`\n```\n\n但是遇到这种情况，就解析不出来了: \n\n```typescript\n// importer.js\nimport { named } from 'my-lib';\n\n// my-lib.js\nvar myLib = exports;\nmyLib.named = 'you can\\'t see me';\n```\n\n在这些情况下，你可以指定自定义命名导出：\n\n```typescript\ncommonjs({\n  namedExports: {\n    // 左边可以是绝对路径（相对于当前工作目录），也可以node_modules模块\n    'my-lib': [ 'named' ]\n  }\n})\n```\n\n自动检测出口的包：\n\n```typescript\nconst PACKAGES_TO_AUTO_DETECT_EXPORTS = [\n  path.join('react', 'index.js'),\n  path.join('react-dom', 'index.js'),\n  'react-is',\n  'prop-types',\n  'scheduler',\n  'rxjs',\n  'exenv',\n  'body-scroll-lock'\n]\n\n// 获取导出名称\nfunction detectExports(root: string, id: string): string[] | undefined {\n  try {\n    const fileLoc = resolveFrom(root, id)\n    if (fs.existsSync(fileLoc)) {\n      return Object.keys(require(fileLoc)).filter((e) => e[0] !== '_')\n    }\n  } catch (err) {\n    // ignore\n  }\n}\n```\n\n> ES6 模块的import命令可以加载 CommonJS 模块，但是只能整体加载，不能只加载单一的输出项。\n>\n> 配置namedExports，才可以使用命名导入。\n\n![3](3.png)\n"
  },
  {
    "path": "41-50/commit-41-50.md",
    "content": "# 41 - ae3c83a 修改特殊路径名称\n\n处理特殊路径```__```改为```@```\n\n### hmr.ts\n\n```typescript\n- if(ctx.path !== '/__hmrClient')\n+ if(ctx.path !== '/@hmr')\n```\n\n#### 关于js/vue文件的重加载\n\n![映射](./import.png)\n\n在commit-39的时候，分析不出其行为，现在根据修改后的代码得出(vue文件调用的```handleVueSFCReload```就不说了，标题上是为了说明文件改动的范围仅影响```js```|```vue```)：\n\n1. ```importerMap```查看是否有该```url请求文件路径```的值。\n\n2. 有，则取出其完整路径```importee```。\n\n3. 调用```walkImportChain```：还没写完，目前支持的是把Vue和普通js文件分类(然而，vue文件不会进入到上图的①|②)。\n\n4. ```importee```路径下的文件，每一个都调用方法(还没写)\n\n#### 回顾一下流程\n\n1.index.html请求main.js\n\n2.服务器收到main.js请求，经过洋葱模型的插件(因为是js文件，所以只匹配这部分的代码)：```koa-static```返回文件(可能是流的读取方式，所以需要一个方法，加载完毕再交给```modules(自定义插件)```去处理)，```modules(自定义插件)```匹配到```js```文件，改写```import```。\n\n```typescript\n// we are doing the js rewrite after all other middlewares have finished;\n    // this allows us to post-process javascript produced by user middlewares\n    // regardless of the extension of the original files.\n    if (\n      ctx.response.is('js') && // 文件类型\n      // skip special requests (internal scripts & module redirects)\n      !ctx.path.startsWith(`/@`) && // 特殊请求\n      // only need to rewrite for <script> part in vue files\n      !(ctx.path.endsWith('.vue') && ctx.query.type != null)\n    ) {\n      await initLexer\n      ctx.body = rewriteImports( // 改写Import句柄\n        await readBody(ctx.body),\n        ctx.url.replace(/(&|\\?)t=\\d+/, ''), // 更改参数t（防止get缓存）\n        ctx.query.t // 更改参数t（防止get缓存）\n      )\n    }\n```\n\n看不懂吗？那就一句简单的👇。\n\n**所有js文件的完整路径与url请求路径的映射**。\n\n#### 小知识\n\n```typescript\n/^[^\\/\\.]/.test(id)\n\n// ./asd false\n// .git false\n// /asd false\n```\n\n在```modules(自定义插件中)```，利用该正则，判断是不是请求```node_modules```的文件，是则把请求路径改写成为```/@modules/${id}```。\n\n#### 坑点\n\n如果安装的模块不是```esm```类型的，那么无法使用，因为```vite```就是基于```esm```进行的。\n\n#### 总结\n\n建立了js映射的关系，在文件改动的时候，触发js文件的```reload```（未完善）。这里可以回顾一下已经完善了的```vue```改动：\n\n1.script的不同，通知```reload```\n\n2.template的不同，通知```rerender```\n\n4.style中的scoped不同，通知```reload```\n\n5.style有不同（顺序的方式，意味着没有优化），通知```style-update```\n\n6.删除多余的style，通知```style-remove```\n\n```typescript\nasync function handleVueSFCReload(file: string, servedPath: string) {\n    const cacheEntry = vueCache.get(file)\n    vueCache.del(file)\n\n    const descriptor = await parseSFC(root, file)\n    if (!descriptor) {\n      // read failed\n      return\n    }\n\n    const prevDescriptor = cacheEntry && cacheEntry.descriptor\n    if (!prevDescriptor) {\n      // the file has never been accessed yet\n      return\n    }\n\n    // check which part of the file changed\n    if (!isEqual(descriptor.script, prevDescriptor.script)) {\n      notify({\n        type: 'reload',\n        path: servedPath\n      })\n      return\n    }\n\n    if (!isEqual(descriptor.template, prevDescriptor.template)) {\n      notify({\n        type: 'rerender',\n        path: servedPath\n      })\n      return\n    }\n\n    const prevStyles = prevDescriptor.styles || []\n    const nextStyles = descriptor.styles || []\n    if (prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped)) {\n      notify({\n        type: 'reload',\n        path: servedPath\n      })\n    }\n    const styleId = hash_sum(servedPath)\n    nextStyles.forEach((_, i) => {\n      if (!prevStyles[i] || !isEqual(prevStyles[i], nextStyles[i])) {\n        notify({\n          type: 'style-update',\n          path: servedPath,\n          index: i,\n          id: `${styleId}-${i}`\n        })\n      }\n    })\n    prevStyles.slice(nextStyles.length).forEach((_, i) => {\n      notify({\n        type: 'style-remove',\n        path: servedPath,\n        id: `${styleId}-${i + nextStyles.length}`\n      })\n    })\n  }\n```\n\n\n\n# 42 - 3e5076d\n\n## 小知识点\n\nhttps://zh.javascript.info/regexp-multiline-mode\n\n多行匹配\n\n```typescript\nlet str = `1st place: Winnie\n2nd place: Piglet\n33rd place: Eeyore`;\n\nalert( str.match(/^\\d+/gm) ); // 1, 2, 33\n```\n\n## package.json\n\n增加@babel/parser。\n\n## client.ts\n\n之前请求文件，参数t为客户端的时间戳，现在更改为服务器给的时间戳（统一交给服务器处理）：\n\n```typescript\n- const { type, path, id, index } = JSON.parse(data)\n+ const { type, path, id, index, timestamp } = JSON.parse(data)\n\n// 例\n- import(`${path}?type=template&t=${Date.now()}`)\n+ import(`${path}?type=template&t=${timestamp}`)\n\n```\n\n更改事件名称：\n\n```reload``` -> ```vue-reload```\n\n```rerender``` -> ```vue-rerender```\n\n```style-update``` -> ```vue-style-update```\n\n新增事件：\n\n```js-update```\n\n### ```js-update```\n\n还没完善，可以看到```hot```，对于js文件的hmr仅仅是重新拉取再运行一次（如果是有状态的，状态还是会继续保留，且出现重复，期待后续修复）\n\n## ```hmr.ts```\n\n更换事件名称，补上```isHotBoundary```方法，并更改名称为```isHMRBoundary```。\n\n## 总结\n\n为了```js```文件的```hmr```做准备\n\n\n\n# 43 - c11cfc8 优化寻找包的方式\n\n之前使用```require()```，现在读取```package.json```文件，识别```module```、```main```字段，如果没有则直接寻找```index.js```文件。\n\n\n\n# 44 - b100683 优化sourcemap获取名\n\n在```sourcemap```功能，使用```path.basename```，获取路径的名称。\n\n![sourcemap](./sourcemap.png)\n\n\n\n# 45 - 35b23e1 修复css bug\n\n```\n+ await resolveCompiler\n```\n\n为```style lang=\"x\"``` 做准备\n\n\n\n# 46 - eb5af6a 修改README\n\nchore: 更新reademe。把vue改成vue3。（莫非尤大想兼容v2?）\n\n\n\n# 47 - e1cba33 v0.4.0\n\n## package.json\n\nv4.0.0发布，这里讨论到发布，看不大懂尤大的ci。\n\nhttps://hub.docker.com/r/vuejs/ci\n\n\n\n# 48 - 169e31f 代码整理\n\n## 重构监听文件的方式\n\n把```server/plugins/hmr.ts```的监听文件变化的代码，移动到```server/index.ts```，通过传递参数```FSWatcher```给```hmr.ts```的方式使用。\n\n其次可以暴露给各种```plugins```使用，比如commit-49中，监听文件变动后删除缓存。\n\n\n\n# 49 - 868aa21  modules使用缓存\n\n## ```server/plugins/modules.ts```\n\n利用watch，监听文件变动，变动的文件，删除缓存。因为变动的文件，需要更新。\n\n缓存的文件有：\n\n1. ```/index.html```\n2. 普通``` .js```文件\n\n除此之外，还有一些补丁，比如在重写普通```js文件```的```import```句柄时，要排除```.map``` 文件。\n\n比如在``` rewriteImports```出现报错的时候，捕获该错误，输出```e```。\n\n\n\n# 50 - aa7c8d0 添加debug包\n\n> 在windows中无法使用。\n\n## package.json\n\n添加```debug@4.1.1```，去除```console.log```，增强提示。添加```chalk```，增强提示，用在```node/build.ts```。\n\n从尤大写的提示语句，可以发现是为了方便调试，因为目前还测试覆盖还很低，需要一些提示去调试。\n\n涉及：\n\n1. ```server/plugins/hmr.ts```\n2. ```server/plugins/modules.ts```\n3. ```serve.ts```\n\n这块也是方便了我们去查看vite到底做了些啥，可以有一个反馈。\n\nhttps://www.npmjs.com/package/chalk\n"
  },
  {
    "path": "411-420/411-420.md",
    "content": "# 411 - 47bfc41 build resolve去除`optimizedModule`\n\n插件去除对`optimizedModule`的处理，即构建模式不会使用被`optimizedModule`处理过的包。\n\n> 什么原因？该功能未稳定，先撤销掉不使用。（至少我完全使用不了，是不是应该server也去除... 手动命令行触发）\n\n\n\n# 412 - 79a55b5 改善`optimizer`输出\n\n通过命令行方式运行`optimizeDeps`，才会将错误输出。\n\n利用`ora`包，loading构建状态。\n\n现在翻译一下尤大的目的（我不喜欢依赖这个词，其实就是`package.json`的` dependencies`）：\n\n```\n// 要优化的依赖。目标是预捆绑以下类型的依赖:\n// 1. 是CommonJS模块\n// 2. 拥有引入相对路径文件的import (e.g. lodash-es, lit-html)\n//    如 export { default as add } from './add.js';\n// 3. Has imports to bare modules that are not in the project's own deps\n//    (i.e. esm that imports its own dependencies, e.g. styled-components)\n```\n\n### `npm`是如何处理包的不同版本呢？\n\n![1](1.png)\n\n假如我的依赖里有3个不同版本的`lodash-es`，`.package_versions.json`会记录哪个包才是我们使用的依赖，比如现在是`4.17.13`，就会建立`lodash-es`与`_lodash-es@4.13@lodash-es`的软链（看到了蓝色的符号了吗？`lodash-es`文件前面的那个标记）。\n\nhttps://toutiao.io/posts/freqyei/preview\n\n```json\n// .package_versions.json\n{\n  \"lodash-es\": [\n    \"4.17.13\" // 当前依赖的版本\n  ]\n}\n```\n\n#### 如何在`vite`项目内的`playground`，运行本地`vite`?\n\n**step1**: 在`node_modules/.bin`创建如下文件：\n![2](2.png)\n\n**step2**: 在`vite`项目运行`yarn link`，再在`vite/playground`项目使用`yarn link vite`:\n\n![3](3.png)\n\n**step3**: 利用`node_modules .bin`特性，在`vite/plaground/package.json`添加`vite`命令。\n\n**step4**: 在`vite/playgrond`项目，`npm run dev`：\n\n![4](4.png)\n\n#### 我只想通过一步直接运行命令敲入`vite`运行，如何处理？\n\n**需要达成的效果**：所有项目都可以直接使用`vite-local`（自定义名称，我为了区分本地和`npm`服务器上的）调用本地，**不**需要任何配置。\n\n![7](7.png)\n\n查看`npm`的环境变量，在所在位置`yarn link vite`，在里面创建`vite-local.cmd`(windows系统)，然后我们就**可以在任何项目中使用`vite-local`调用本地了~**，省去了所有步骤，而且又不用新增命令行，多香。\n\n```powershell\n# vite-local.cmd\n\n@ECHO off\nSETLOCAL\nCALL :find_dp0\n\nSET _maybeQuote=\"\nIF EXIST \"%dp0%\\node.exe\" (\n  SET \"_prog=%dp0%\\node.exe\"\n) ELSE (\n  SET _maybeQuote=\n  SET \"_prog=node\"\n  SET PATHEXT=%PATHEXT:;.JS;=;%\n)\n\n%_maybeQuote%%_prog%%_maybeQuote%  \"%dp0%\\node_modules\\vite\\bin\\vite.js\" %*\nENDLOCAL\nEXIT /b %errorlevel%\n:find_dp0\nSET dp0=%~dp0\nEXIT /b\n```\n\n![5](5.png)\n\n![6](6.png)\n\n\n\n# 413 - b1726d8 fix [#137](https://github.com/vitejs/vite/issues/137)，`esbuild sourcemap`文件名称不正确\n\n升级`esbuild`，使用`sourcefile`指定`sources`名称。\n\n![8](8.png)\n\n\n\n# 414 - e533966 更新bug报告模板\n\n Triage 这个单词的意思，“根据紧迫性和救活的可能性等在战场上决定那些人优先治疗的方法”\n\n所以Pending Triage 是什么意思... 我也不知道\n\n\n\n# 415 - 9a31646 workflow 调试模式下使用`sourcemap`\n\n假如现在`vite`报错了，不使用`sourcemap`，错误会指向被`ts`编译后的`js`文件。\n\n为了更清晰知道哪里报错，引入[`source-map-support`包](https://www.npmjs.com/package/source-map-support)，在`tsconfig.base.json`设置`sourceMap: true`。\n\n**原理**：`ts`生成`xxx.map`，node通过`source-map-support`包，内部利用V8引擎的stack trace API，转换错误信息。\n\n\n\n# 416 - 9b55701 避免source maps文件被加入发布\n\n指定`package.json` files:\n\n```typescript\n\"files\": [\n    \"bin\",\n    \"dist/**/*.js\",\n    \"dist/**/*.d.ts\",\n    \"hmr.d.ts\"\n  ]\n```\n\n> `dist/**/*.js`包括0~n+级，如`dist/cli.js` `dist/server/index.js` `dist/XXX/XXX/XXX/XXX/XXX/index.js`\n\n两个连续的星号`**`在匹配全路径名的时候可能有特殊含义：\n\n- 规则以两个星号`**`开头，后接一个斜杠，这样的规则会在所有路径或子路径中尝试进行匹配。比如，`**/foo`会匹配到文件`foo`或者目录`foo`，无论它在哪个目录；`foo`这条规则同样会尝试匹配所有路径中的文件`foo`或者目录`foo`。`**/foo/bar`规则会匹配任意文件或目录`foo`下直接跟的文件`bar`或目录`bar`\n- 如果规则中间有连续的两个星号`**`，那这条规则会匹配下面的所有东西。比如`abc/**`会匹配目录`abc`下的所有文件或目录，当然，这里的目录`abc`是相对于`.gitignore`文件位置而言的，无限递归\n- 如果规则是`斜杠/`后跟两个星号，然后再跟一个斜杠的形式，这里的两个星号就会匹配`0+`个目录，这里的`0+`是指可以没有，也可以是多个。再举个例子，比如`a/**/b`会匹配`a/b`、`a/x/b`、`a/x/y/b`这些\n- 其他形式的连续星号都认为是非法的\n\nfiles field: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files\n\n\n\n# 417 - 88284f1 只需要对运行在node端的代码进行sourcemap\n\n去除`tsconfig.base.json` `sourceMap`，在`src/node/tsconfig.json`添加`sourceMap`\n\n> `client.ts` 也没有任何`inline sourcemap`\n\n\n\n# 418 - a73754c 修复`depOptimization hash`\n\n`depOptimization hash`，应该只包括`package.json`的`dependencies`而并非整个`package.json`。\n\n> 就是只监听依赖变动，决定是否调用optimization，属于BUG\n\n`@vue/*`包，不再与`vue`进行绑定，我们只要在`vue`获取即可。\n\n**反正安装`vue`也会帮我们安装相同版本号的依赖**。\n\n![10](10.png)\n\n#### 关于`vue`包导出的内容\n\n![9](9.png)\n\n> 如果用户拥有本地`vue`包，如果`@vue/*`包的版本与`vue`包不一致，将会抛出错误。\n>\n> 什么时候才会不一样？看412，`npm`如何帮你区分版本号。\n>\n> `vue`是通过脚本修改包的版本&发布，达到统一。\n\n[webpack4 对`package.json`的`sideEffects`支持](https://stackoverflow.com/questions/49160752/what-does-webpack-4-expect-from-a-package-with-sideeffects-false) （400那一文章也有写）\n\n`rollup`通过使用`rollup-plugin-node-resolve`支持`sideEffects`转换为`moduleSideEffects`\n\n[issues-2593](https://github.com/rollup/rollup/issues/2593)\n\n`vite`会读取`package.json`:\n\n![11](11.png)\n\n\n\n# 419 - f7c434c cva ignore node_modules\n\n`cva`不应该在发布的时候包括`node_modules`\n\n![12](12.png)\n\n> `npm`默认读取`.gitignore`，所以`vite`不用添加`.npmignore`\n\n\n\n# 420 - eb15db3 fix optimized module id拓展名应去除\n\n- 处理` optimized module`的`resolveBareModule`方法去掉`js`的拓展名称（[410](https://github.com/Kingbultsea/vite-analysis/blob/master/401-410/401-410.md#%E6%94%B9%E5%8A%A8%E4%B8%80)有提及看不懂这个`js`的添加，是个BUG），这个BUG修正后，`optimize`命令有效了!（详 **改动一**）\n- `node/utils/fsUtils.ts`新增`lookupFile`，由`node/depOptimizer.ts`给迁移过去（详 **改动二**）\n\n### 改动一 optimize是什么？\n\n```typescript\nexport type ResolvedConfig = UserConfig & { __path?: string }\n// __path为vite.config.ts的路径 resolveOptions会添加\n\nexport interface OptimizeOptions extends ResolvedConfig {\n  force?: boolean\n}\n// --force 可以无视hash\n```\n\n`optimize`会根据`vite.config.ts` & `package.json dependencies字段` & (`package-lock.json`|`yarn.lock`|`pnpm-lock.yaml`)的内容生成`hash`，每次调用`optimize`都会根据这个`hash`的变动(可以添加`--force`强制无视`hash`进行`optimize`)而决定是否运行`optimize`（模块打包）。\n\n接着创建`node_modules/.vite_opt_cache`文件夹，检测`package.json`，如果没有则**终止任务**。\n\n获取`package.json dependencies` 指引名称`deps`，检测是否存在，如果不存在则把`hash`写入`node_modules/.vite_opt_cache`中，**任务终止**。\n\nNo dependencies listed in package.json. Skipping.\n\n![13](13.png)\n\n创建`resolver`，`const resolver = createResolver(root, config.resolvers, config.alias)`。\n\n筛选`deps`(依赖)，调用`resolveNodeModule`寻找每个`dep`的入口(默认`module`，没有则`main`)，读取入口内容，把内容编译成AST树后，分析AST树的`imports` `exports`语句，**如果没有`exports`语句**(表示这个包是`cjs`类型的模块)|**`imports`语句所引入的模块id**(会被`resolver.alias(id)`所控制，保持与开发或者构建的行为一致性)**在deps不存在或以`.`为开头**(如`import './abc.js'`)，筛选到`qualifiedDeps`(被筛选后的依赖)中。\n\n- `cjs`模块 \n\n-  包括相对路径的模块(如`lodash-es` `export { default as add } from './add.js'`) \n- 引入了不包含在本地（不存在于用户在开发的项目的`package.json dependencies`）的依赖\n\n符合以上三点，都会被筛选进`qualifiedDeps`中。\n\n如果`qualifiedDeps`长度为0，则把`hash`写入`node_modules/.vite_opt_cache`中，**任务终止**。\n\nNo listed dependency requires optimization. Skipping.\n\n`const preservedDeps = deps.filter((id) => !qualifiedDeps.includes(id))`，不存在于`qualifiedDeps`的`deps`，会被筛选进`preservedDeps `，`preservedDeps`会被标记为`external`，不会被打包进`rollup`中。\n\n`qualifiedDeps`被转换为`{name: name}`的形式，用于`rollup`的`input`入口中，如`{'lodash-es': 'lodash-es'}`，`chunk.fileName`会为`lodash-es`，即`key`，不能使用数组，会被配置为`module`入口，如`lodash`， [rollup.input](https://rollup.docschina.org/guide/en/#input)。\n\n![配置为数组的后果](14.png)\n\nrollup插件为`createBaseRollupPlugins`，[410-包含的插件](https://github.com/Kingbultsea/vite-analysis/blob/4adcbd8e64a7636ed310e186b57aa628d49c64a4/401-410/401-410.md#%E6%94%B9%E5%8A%A8%E4%B8%89)（SFC组件库也支持，JSON这些都可以，一切转换为`cjs`）。\n\n```typescript\nconst rollup = require('rollup') as typeof Rollup\n    const bundle = await rollup.rollup({\n      input,\n      external: preservedDeps,\n      treeshake: { moduleSideEffects: 'no-external' }, // 不读取包的sideEffect，在构建的时候才会读取，no-external表示external字段下的sideEffect为false，也就是cjs 相对路径这些会被完整打包，lodash-es就全部都被打包进去了，反正我们后面真正构建的时候还不是会tree-shaking掉嘛\n      onwarn(warning, warn) {\n        if (warning.code !== 'CIRCULAR_DEPENDENCY') {\n          warn(warning)\n        }\n      },\n      ...config.rollupInputOptions,\n      plugins: await createBaseRollupPlugins(root, resolver, config)\n    })\n\n    const { output } = await bundle.generate({\n      ...config.rollupOutputOptions,\n      format: 'es',\n      exports: 'named',\n      chunkFileNames: 'common/[name]-[hash].js'\n    })\n```\n\n最后遍历`output`，写进`node_modules/.vite_opt_cache`中。\n\n```typescript\nconst optimized = []\nfor (const chunk of output) {\n  if (chunk.type === 'chunk') {\n    const fileName = chunk.fileName\n    const filePath = path.join(cacheDir, fileName)\n    await fs.ensureDir(path.dirname(filePath))\n    await fs.writeFile(filePath, chunk.code)\n    if (!fileName.startsWith('common/')) {\n      optimized.push(fileName.replace(/\\.js$/, ''))\n    }\n  }\n}\n```\n\n![15](15.png)\n\n#### 为什么不符合那三个条件的包都要被external?\n\n比如你现在有依赖`A`和`lodash-es`，A包只有一句`export default console.log`（我愿意称呼这种包为**中介包**，如`vue`），那他不符合那三个条件，也不会被优化打包进`node_modules/.vite_opt_cache`，但是万一`lodash-es`包引入了`A`包呢？所以要标记起来，让rollup知道`A`包是`external`不用引入，同时也可以`tree-shaking`掉。\n\n```typescript\n// vue就是中介包\nimport { warn } from '@vue/runtime-dom';\nexport * from '@vue/runtime-dom';\n\n// This entry exports the runtime only, and is built as\nconst compile = () => {\n    if ((process.env.NODE_ENV !== 'production')) {\n        warn(`Runtime compilation is not supported in this build of Vue.` +\n            ( ` Configure your bundler to alias \"vue\" to \"vue/dist/vue.esm-bundler.js\".`\n                ) /* should not happen */);\n    }\n};\n\nexport { compile };\n```\n\n以上完毕~\n\n> lock文件包括`devDependencies`的版本信息，尤大不希望监听`package.json devDependencies字段`，也就是说，我们在开发的时候对于dev想要小版本的更新而不添加进lock锁定版本的情况下，`vite`优化打包的缓存可以继续存在（算了... 我觉得是`lock file`已经包括了`dev`的，尤大没有去除罢了... 以上情况只能说是想多了）。\n>\n> [什么时候我们不应该使用锁文件](https://blog.csdn.net/weixin_34174105/article/details/91443977)，`package-lock.json`默认不发布`npm`，[`package json files`](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files)。\n>\n> `npm --dry-run`可以查看哪些文件会被发布到`npm`上。\n\n#### 发布的cli不锁定版本好处是什么？\n\n用户可以进行更新，很多情况下，比如babel这些依赖会有关键补丁。而且可以减少node_modules的体积大小（如A包依赖`vite ^0.14.1`和B包依赖了`vite ^0.14.2` 都会被更新到`vite 0.15.0`**以下**）\n\n### 改动二 `lookupFile`方法\n\n```typescript\nexport function lookupFile(dir: string, formats: string[]): string | undefined {\n  for (const format of formats) {\n    const fullPath = path.join(dir, format)\n    if (fs.existsSync(fullPath)) {\n      return fs.readFileSync(fullPath, 'utf-8')\n    }\n  }\n  const parentDir = path.dirname(dir)\n  if (parentDir !== dir) {\n    return lookupFile(parentDir, formats)\n  }\n}\n```\n\n寻找在**参数一****路径中存在的参数二数组下的文件**，如果数组中的文件都没有被找到，则递归寻找**参数一**的父路径。\n\n迁移后还将被用于`node/utils/resolveVue.ts`寻找vue包\n\n[path.dirname示例](https://vimsky.com/examples/usage/node-js-path-dirname-method.html)\n\n"
  },
  {
    "path": "421-430/421-430.md",
    "content": "# 421 - d7fb6a9 optimize功能中不支持的拓展不应打包 & 修复无法引入如`css`的包问题\n\n英文msg: fix support for `non-js` module imports，即修复无法引入非`js`模块包的问题\n\n改动部分：\n\n- `depOptimizer.ts` 不在拓展名单内的包不应该被打包\n- `server/serverPluginModuleRewrite.ts`，对于入口文件不为`/\\.(?:(?:j|t)sx?|vue)$/`的包，要添加import参数，从而可以import引入`css`包\n- `mode/resolver.ts`: 拆分`resolveNodeModule`为：处理包入口(`resolveNodeModuleEntry`) 和 处理文件拓展名`resolveNodeModule`(原名)，拆分出来处理文件拓展名功能给`depOptimizer.ts`\n\n### `depOptimizer.ts` 不在拓展名单内的包不应该被打包\n\n支持预打包优化的**包入口文件**格式`['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']`。\n\n`vite`会寻找依赖的id的main入口，检测其是否能够通过`require.resolve`处理返回入口路径(默认main入口)，若无，遍历`['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']`添加上后缀后再重新调用`require.resolve`寻找。\n\n#### 话说是不是不应该使用`require.resolve`呢？毕竟会返回包的`main`入口\n\n雀氏... 留坑，serve的时候用的也是module，应该保持一致才对（不是人人写包都遵循规则的）。\n\n#### 所以optimize的作用到底是什么？\n\n`vite`预打包的是`cjs`、引入了相对路径、包内引入了不在用户依赖的包。\n\n这里有一个场景：\n\n你引入了ant-pro的一个组件，ant-pro的import 样式链有30多个，而浏览器只支持并行6个请求，你如何优化？\n\n`optimize`就是为此而生，把所有东西打包到一个`esm`模块中（支持具名import [410-改动三](https://github.com/Kingbultsea/vite-analysis/blob/4adcbd8e64a7636ed310e186b57aa628d49c64a4/401-410/401-410.md#%E6%94%B9%E5%8A%A8%E4%B8%89)），可以一次性把包内所有请求合并成一个。\n\n### `server/serverPluginModuleRewrite.ts`\n\n对于入口文件不为`/\\.(?:(?:j|t)sx?|vue)$/`的包，添加`?import`参数。\n\n因为`css`要区分是不是import的方式引入(这只是例子，`json`或者其他静态资源也是需要区分)，从而返回js内容还是文件原本本身内容，`isImportRequest`就是检测`import`参数是否存在而判断是不是import的方式引入。\n\n> 也就是可以引入入口为`.css`的包了\n\n### 该给`optimizer`起个中文名称了！\n\n**依赖瀑布流优化**、cjs模块转换esm，简称**依赖优化**（我没看过`vite`的文档... 见谅 后续都以这个名称来说）\n\n\n\n# 422 - 2dd45af 对于有拓展名的才加入`?import`参数\n\n421中`server/serverPluginModuleRewrite.ts`，对没有拓展名称的文件也加入了`?import`，现在没有拓展名称的包入口，将不再添加`?import`。\n\n即返回原内容。\n\n\n\n# 423 - e49742e 依赖优化应该检测`module`入口 & 新增检测包入口的`exports` `package.json`字段\n\n421中提到的坑，现在修复了。现在使用`resolveNodeModuleEntry`寻找`module`入口，检测`module`入口文件的拓展名称是否符合`['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']`，符合则打包，不符合则作为`external`。\n\n### `node/resolver.ts`\n\n`resolveNodeModuleEntry`: **新增**，检测包`package.json`的`exports`字段作为入口，没有则从原本的`module` | `main`字段获取。\n\n[package-exports](https://webpack.js.org/guides/package-exports/)\n\n\n\n# 424 - f7a419c `deepImport`无法用于依赖优化中，给出错误提示\n\n因为依赖优化是打包成一个文件的，使用如`import 'lodash/add.js'`将会触发提示(前提该依赖在**依赖优化**中)。\n\n![1](1.png)\n\n```powershell\n[vite] Avoid deep import \"lodash-es/add.js\" since \"lodash-es\" is a pre-optimized dependency.\nPrefer importing from the module directly.\nImporter: /App.vue\n```\n\n> 所以说明了开源真不是一件容易的事情，错误提醒这些一定要有，不是每个人都能很好地去阅读了解文档的。\n\n\n\n# 425 - 0819bcb 加载`postcss config`出现错误后的提醒 [#140](https://github.com/vitejs/vite/issues/140)\n\n如`postcss.config`加载失败或异常，增加详细的错误提示。\n\n```typescript\nexport async function loadPostcssConfig(root: string): Promise<Result | null> {\n  if (cachedPostcssConfig !== undefined) {\n    return cachedPostcssConfig\n  }\n  try {\n    const load = require('postcss-load-config') as typeof postcssrc\n    return (cachedPostcssConfig = await load({}, root))\n  } catch (e) {\n    console.error(chalk.red(`[vite] Error loading postcss config:`))\n    console.error(e)\n    return (cachedPostcssConfig = null)\n  }\n}\n```\n\n\n\n# 426 - a6a76a7 `index.html`支持`hmr`，调用触发`full-reload`\n\n通过检测`request`是否为`/index.html`，而进行`full-reload`。\n\n```typescript\nwatcher.on('change', async (file) => {\n  const timestamp = Date.now()\n  if (resolver.fileToRequest(file) === '/index.html') {\n    send({\n      type: 'full-reload',\n      path: '/index.html',\n      timestamp\n    })\n    console.log(chalk.green(`[vite] `) + `page reloaded.`)\n  }\n    // ...\n})\n```\n\n> `serveStaticPlugin`，会在开始通过`url`查看页面时（就是首个服务请求），把request调整为`/index.html`;入口文件并不可配置，默认为工作目录下的index.html文件·。\n\n\n\n# 427 - 3504b29 changelog\n\n# [0.15.0](https://github.com/vuejs/vite/compare/v0.14.4...v0.15.0) (2020-05-14)\n\n### Bug Fixes\n\n- 修复无法引入非`js`模块包的问题（没有添加import参数） ([d7fb6a9](https://github.com/vuejs/vite/commit/d7fb6a9e8a6caf4041a2a602564583e4c34346e0)), closes [#132](https://github.com/vuejs/vite/issues/132)\n- 跟进esbuild source文件路径不正常的问题 ([#141](https://github.com/vuejs/vite/issues/141)) ([b1726d8](https://github.com/vuejs/vite/commit/b1726d84e1bf694797f30c62ca509644577ef583)), closes [#137](https://github.com/vuejs/vite/issues/137)\n- 输出postcss config错误信息 (close [#140](https://github.com/vuejs/vite/issues/140)) ([0819bcb](https://github.com/vuejs/vite/commit/0819bcb597673c329e5699b91295b22dd07c4dc7))\n- 对于有拓展名的包入口才加入`?import`参数 ([2dd45af](https://github.com/vuejs/vite/commit/2dd45affef2ece21821b208910fc00c23775c331))\n- 遵守`package.json`的`exports`字段意义 ([e49742e](https://github.com/vuejs/vite/commit/e49742e40dcc385c03c1e16a3a0a3fad60fcb417))\n\n### Features\n\n- 服务开启自动进行依赖优化 ([49a44b6](https://github.com/vuejs/vite/commit/49a44b648f263ff058f730913ea1ee6c62e3cd2d))\n- 编辑`index.html`将触发页面重新加载 ([a6a76a7](https://github.com/vuejs/vite/commit/a6a76a7946bd4d4c68edd22eb0295f758ea48990))\n\n\n\n# 428 - 43b51ba v0.15.0\n\nrelease `vite` v0.15.0\n\n\n\n# 429 - c1dc3ce `cva` v1.5.0\n\nrelease `cva` v1.5.0\n\n\n\n# 430 - b03d1c3 寻找不到`postcss.config`的情况下不应该输出警告，因为用户可能不配置\n\n检测报错信息。\n\n```typescript\nexport async function loadPostcssConfig(root: string): Promise<Result | null> {\n  if (cachedPostcssConfig !== undefined) {\n    return cachedPostcssConfig\n  }\n  try {\n    const load = require('postcss-load-config') as typeof postcssrc\n    return (cachedPostcssConfig = await load({}, root))\n  } catch (e) {\n    if (!/No PostCSS Config found/.test(e.message)) {\n      console.error(chalk.red(`[vite] Error loading postcss config:`))\n      console.error(e)\n    }\n    return (cachedPostcssConfig = null)\n  }\n}\n```\n\n> 报错信息来源于`postcss-load-config`包，`vite`并不检测你的文件是否存在`postcss.config`，这表明不管什么情况下都会执行`loadPostcssConfig`。\n\n"
  },
  {
    "path": "431-440/431-440.md",
    "content": "# 431 - 0bfd283 changelog\n\n [0.15.1](https://github.com/vuejs/vite/compare/v0.15.0...v0.15.1) (2020-05-14)\n\n### Bug Fixes\n\n- 当 `postcss` 配置不存在时不发出警告 ([b03d1c3](https://github.com/vuejs/vite/commit/b03d1c3091ac870ad6b86c796ee584417393fb6e))\n\n\n\n# 432 - e958975 v0.15.1\n\nrelease `vite` v0.15.1\n\n\n\n# 433 - 39a7640 重新指定`@vue/`下的包路径\n\n`import '@vue/runtime-dom'` -> `import '@vue/runtime-dom/dist/runtime-dom.esm-bundler.js'`\n\n`import '@vue/runtime-core'` -> `import '@vue/runtime-core/dist/runtime-core.esm-bundler.js'`\n\n`import '@vue/reactivity'` -> `import '@vue/reactivity/dist/reactivity.esm-bundler.js'`\n\n`import '@vue/shared'` -> `import '@vue/shared/dist/shared.esm-bundler.js'`\n\n`vue`为`runtime-dom`。\n\n> `serverPluginModuleResolve`下的`serve`，是因为watch默认监听root下的文件变动。\n>\n> 如果使用了`vite`依赖下的`vue`，将会调用`serve`（一般来说`vite`是全局安装的，所以依赖不会在开发者的项目目录下）。\n\n\n\n# 434 - ead7c93 changelog\n\n## [0.15.2](https://github.com/vuejs/vite/compare/v0.15.1...v0.15.2) (2020-05-14)\n\n### Bug Fixes\n\n- 修复非本地vue解析 ([39a7640](https://github.com/vuejs/vite/commit/39a76408c8722a7eaa3f371d2e08114eea25c82a))\n\n\n\n# 435 - ecf6daa v0.15.2\n\nrelease `vite` v0.15.2\n\n\n\n# 436 - 75fcb8e snowpack MIT License\n\n```typescript\n/**\n * Named exports detection logic from Snowpack\n * MIT License\n * https://github.com/pikapkg/snowpack/blob/master/LICENSE\n */\nconst PACKAGES_TO_AUTO_DETECT_EXPORTS = [\n  path.join('react', 'index.js'),\n  path.join('react-dom', 'index.js'),\n  'react-is',\n  'prop-types',\n  'scheduler',\n  'rxjs',\n  'exenv',\n  'body-scroll-lock'\n]\n```\n\n### 再详细谈谈需要明确表示导出命名的例子\n\n以rxjs为例，查看rxjs源码，不是`@rollup/plugin-commonjs`[盲点](https://github.com/Kingbultsea/vite-analysis/blob/4adcbd8e64a7636ed310e186b57aa628d49c64a4/401-410/401-410.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%91%BD%E5%90%8D%E5%AF%BC%E5%87%BA)。\n\n竟然不是了，又为什么需要设置`namedExports`？**这个我也没搞懂...** \n\n然后我去翻该插件的changlog，namedExports的相关信息。\n\n`namedExports` (types) were deprecated and removed at [v13.0.0](https://github.com/rollup/plugins/blob/3ec3ceff0d49ac143a4af5ac54fb6be5425942ac/packages/commonjs/CHANGELOG.md#v1300) in 2020-06-00. \n\n也就是说尤大后续会抛弃这个设定(现在对应的是2020年5月)。\n\n看看`Vitev^2.5.0`的代码:\n\n![1](1.png)\n\n那么`@rollup/plugin-commonjs`的盲点是不是被支持修复了？**是**\n\n> It is a breaking change because we are removing the `namedExports` option, and require at least rollup v2.3.4 or higher. Instead of manually defining named exports, rollup now handles this automatically for you.\n>\n> 这是一个重大更改，因为我们正在删除 `namedExports` 选项，并且至少需要 rollup v2.3.4 或更高版本。与手动定义命名导出不同，rollup 现在会自动为你处理。\n>\n> https://github.com/rollup/plugins/blob/master/packages/commonjs/test/test.js#L618\n\n```typescript\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar Observable_1 = require(\"./internal/Observable\");\nexports.Observable = Observable_1.Observable;\nvar ConnectableObservable_1 = require(\"./internal/observable/ConnectableObservable\");\nexports.ConnectableObservable = ConnectableObservable_1.ConnectableObservable;\nvar groupBy_1 = require(\"./internal/operators/groupBy\");\nexports.GroupedObservable = groupBy_1.GroupedObservable;\nvar observable_1 = require(\"./internal/symbol/observable\");\nexports.observable = observable_1.observable;\nvar Subject_1 = require(\"./internal/Subject\");\nexports.Subject = Subject_1.Subject;\nvar BehaviorSubject_1 = require(\"./internal/BehaviorSubject\");\nexports.BehaviorSubject = BehaviorSubject_1.BehaviorSubject;\nvar ReplaySubject_1 = require(\"./internal/ReplaySubject\");\nexports.ReplaySubject = ReplaySubject_1.ReplaySubject;\nvar AsyncSubject_1 = require(\"./internal/AsyncSubject\");\nexports.AsyncSubject = AsyncSubject_1.AsyncSubject;\nvar asap_1 = require(\"./internal/scheduler/asap\");\nexports.asapScheduler = asap_1.asap;\nvar async_1 = require(\"./internal/scheduler/async\");\nexports.asyncScheduler = async_1.async;\nvar queue_1 = require(\"./internal/scheduler/queue\");\nexports.queueScheduler = queue_1.queue;\nvar animationFrame_1 = require(\"./internal/scheduler/animationFrame\");\nexports.animationFrameScheduler = animationFrame_1.animationFrame;\nvar VirtualTimeScheduler_1 = require(\"./internal/scheduler/VirtualTimeScheduler\");\nexports.VirtualTimeScheduler = VirtualTimeScheduler_1.VirtualTimeScheduler;\nexports.VirtualAction = VirtualTimeScheduler_1.VirtualAction;\nvar Scheduler_1 = require(\"./internal/Scheduler\");\nexports.Scheduler = Scheduler_1.Scheduler;\nvar Subscription_1 = require(\"./internal/Subscription\");\nexports.Subscription = Subscription_1.Subscription;\nvar Subscriber_1 = require(\"./internal/Subscriber\");\nexports.Subscriber = Subscriber_1.Subscriber;\nvar Notification_1 = require(\"./internal/Notification\");\nexports.Notification = Notification_1.Notification;\nexports.NotificationKind = Notification_1.NotificationKind;\nvar pipe_1 = require(\"./internal/util/pipe\");\nexports.pipe = pipe_1.pipe;\nvar noop_1 = require(\"./internal/util/noop\");\nexports.noop = noop_1.noop;\nvar identity_1 = require(\"./internal/util/identity\");\nexports.identity = identity_1.identity;\nvar isObservable_1 = require(\"./internal/util/isObservable\");\nexports.isObservable = isObservable_1.isObservable;\nvar ArgumentOutOfRangeError_1 = require(\"./internal/util/ArgumentOutOfRangeError\");\nexports.ArgumentOutOfRangeError = ArgumentOutOfRangeError_1.ArgumentOutOfRangeError;\nvar EmptyError_1 = require(\"./internal/util/EmptyError\");\nexports.EmptyError = EmptyError_1.EmptyError;\nvar ObjectUnsubscribedError_1 = require(\"./internal/util/ObjectUnsubscribedError\");\nexports.ObjectUnsubscribedError = ObjectUnsubscribedError_1.ObjectUnsubscribedError;\nvar UnsubscriptionError_1 = require(\"./internal/util/UnsubscriptionError\");\nexports.UnsubscriptionError = UnsubscriptionError_1.UnsubscriptionError;\nvar TimeoutError_1 = require(\"./internal/util/TimeoutError\");\nexports.TimeoutError = TimeoutError_1.TimeoutError;\nvar bindCallback_1 = require(\"./internal/observable/bindCallback\");\nexports.bindCallback = bindCallback_1.bindCallback;\nvar bindNodeCallback_1 = require(\"./internal/observable/bindNodeCallback\");\nexports.bindNodeCallback = bindNodeCallback_1.bindNodeCallback;\nvar combineLatest_1 = require(\"./internal/observable/combineLatest\");\nexports.combineLatest = combineLatest_1.combineLatest;\nvar concat_1 = require(\"./internal/observable/concat\");\nexports.concat = concat_1.concat;\nvar defer_1 = require(\"./internal/observable/defer\");\nexports.defer = defer_1.defer;\nvar empty_1 = require(\"./internal/observable/empty\");\nexports.empty = empty_1.empty;\nvar forkJoin_1 = require(\"./internal/observable/forkJoin\");\nexports.forkJoin = forkJoin_1.forkJoin;\nvar from_1 = require(\"./internal/observable/from\");\nexports.from = from_1.from;\nvar fromEvent_1 = require(\"./internal/observable/fromEvent\");\nexports.fromEvent = fromEvent_1.fromEvent;\nvar fromEventPattern_1 = require(\"./internal/observable/fromEventPattern\");\nexports.fromEventPattern = fromEventPattern_1.fromEventPattern;\nvar generate_1 = require(\"./internal/observable/generate\");\nexports.generate = generate_1.generate;\nvar iif_1 = require(\"./internal/observable/iif\");\nexports.iif = iif_1.iif;\nvar interval_1 = require(\"./internal/observable/interval\");\nexports.interval = interval_1.interval;\nvar merge_1 = require(\"./internal/observable/merge\");\nexports.merge = merge_1.merge;\nvar never_1 = require(\"./internal/observable/never\");\nexports.never = never_1.never;\nvar of_1 = require(\"./internal/observable/of\");\nexports.of = of_1.of;\nvar onErrorResumeNext_1 = require(\"./internal/observable/onErrorResumeNext\");\nexports.onErrorResumeNext = onErrorResumeNext_1.onErrorResumeNext;\nvar pairs_1 = require(\"./internal/observable/pairs\");\nexports.pairs = pairs_1.pairs;\nvar partition_1 = require(\"./internal/observable/partition\");\nexports.partition = partition_1.partition;\nvar race_1 = require(\"./internal/observable/race\");\nexports.race = race_1.race;\nvar range_1 = require(\"./internal/observable/range\");\nexports.range = range_1.range;\nvar throwError_1 = require(\"./internal/observable/throwError\");\nexports.throwError = throwError_1.throwError;\nvar timer_1 = require(\"./internal/observable/timer\");\nexports.timer = timer_1.timer;\nvar using_1 = require(\"./internal/observable/using\");\nexports.using = using_1.using;\nvar zip_1 = require(\"./internal/observable/zip\");\nexports.zip = zip_1.zip;\nvar scheduled_1 = require(\"./internal/scheduled/scheduled\");\nexports.scheduled = scheduled_1.scheduled;\nvar empty_2 = require(\"./internal/observable/empty\");\nexports.EMPTY = empty_2.EMPTY;\nvar never_2 = require(\"./internal/observable/never\");\nexports.NEVER = never_2.NEVER;\nvar config_1 = require(\"./internal/config\");\nexports.config = config_1.config;\n//# sourceMappingURL=index.js.map\n```\n\n```typescript\nfunction detectExports(root: string, id: string): string[] | undefined {\n  try {\n    const fileLoc = resolveFrom(root, id)\n    if (fs.existsSync(fileLoc)) {\n      // 输出所有非私有（符号 _ ）的变量  \n      return Object.keys(require(fileLoc)).filter((e) => e[0] !== '_')\n    }\n  } catch (err) {\n    // ignore\n  }\n}\n```\n\n\n\n# 437 - 36a24aa docs 去除web_modules的文档信息\n\n### How is This Different from [Snowpack](https://www.snowpack.dev/)?\n\nSnowpack 2在范围上更接近于Vite-两者都提供捆绑的开发服务器，并且可以捆绑应用程序进行生产。一些显著的区别是：\n\n- 特别是对于Vue，Vite提供内置HMR，而Snowpack只需在任何文件编辑时重新加载页面。由于这两种解决方案都依赖于本地ES导入，因此整个页面重新加载的网络瀑布实际上可能成为编辑到反馈速度的瓶颈。HMR允许你在相当长的开发时间内避免重新加载页面。\n- Vite有点固执己见，旨在最大限度地减少所需的配置量。上面列出的所有功能，如TypeScript、CSS导入和PostCSS支持，都是现成的。\n- 虽然Vite在技术上可以用于开发具有任何框架的应用程序，但其主要重点是尽可能提供最佳的Vue开发体验。支持第三方框架，但不是最优先考虑的。\n\n> 回头查看了一下web_modules 这个功能和vite的**依赖优化**，是一样的，减少网络瀑布。\n\n\n\n# 438 - fc7b978 避免tailwindcss被作用于依赖优化 [#146](https://github.com/vitejs/vite/issues/146)\n\n[#146](https://github.com/vitejs/vite/issues/146)并没有反馈什么有用的信息，看错误信息是打包tailwindcss内存不足（留坑）。\n\n![2](2.png)\n\n\n\n# 439 - 可配置的依赖优化option\n\n改动部分：\n\n- `node/cli.ts`: 去除默认启动依赖优化（迁移进server.listen）\n- `node/config.ts`: 新增`optimizeDeps`开发者选项，`DepOptimizationOptions`类型\n- `node/depOptimizer.ts`: 新增`DepOptimizationOptions`类型，即依赖优化配置选项（详 **新增三**）\n- `node/server/index.ts`: 重写server，只要optimizeDeps.auto不为false，都会启动依赖优化（详 **改动四**）\n\n### 新增三 `DepOptimizationOptions`类型\n\n不管`include`还是`exclude`都是优先那三个符合优化的条件。\n\n```typescript\nexport interface DepOptimizationOptions {\n  /**\n   * 包括的包id（项目依赖下）\n   */\n  include?: string[]\n  /**\n   * 不包括的包id（项目依赖下）\n   */\n  exclude?: string[]\n  /**\n   * 是否在服务启动时自动运行`vite optimize`?\n   * @default true\n   */\n  auto?: boolean\n}\n\n  // Determine deps to optimize. The goal is to only pre-bundle deps that falls\n  // under one of the following categories:\n  // 1. Is CommonJS module\n  // 2. Has imports to relative files (e.g. lodash-es, lit-html)\n  // 3. Has imports to bare modules that are not in the project's own deps\n  //    (i.e. esm that imports its own dependencies, e.g. styled-components)\n  await init\n  const qualifiedDeps = deps.filter((id) => {\n    if (include && !include.includes(id)) {\n      return false\n    }\n    if (exclude && exclude.includes(id)) {\n      return false\n    }\n    if (KNOWN_IGNORE_LIST.has(id)) {\n      return false\n    }\n    const entry = resolveNodeModuleEntry(root, id)\n    if (!entry) {\n      return false\n    }\n    if (!supportedExts.includes(path.extname(entry))) {\n      return false\n    }\n    const content = fs.readFileSync(resolveFrom(root, entry), 'utf-8')\n    const [imports, exports] = parse(content)\n    if (!exports.length) {\n      // no exports, likely a commonjs module\n      return true\n    }\n    for (const { s, e } of imports) {\n      let i = content.slice(s, e).trim()\n      i = resolver.alias(i) || i\n      if (i.startsWith('.') || !deps.includes(i)) {\n        return true\n      }\n    }\n  })\n```\n\n### 改动四 根据auto字段，绑定依赖优化到listen\n\n`cli.ts`会调用`server.listen(port)`。\n\n```typescript\nonst listen = server.listen.bind(server)\n  server.listen = (async (...args: any[]) => {\n    if (optimizeDeps.auto !== false) {\n      await require('../depOptimizer').optimizeDeps(config)\n    }\n    return listen(...args)\n  }) as any\n\n  return server\n```\n\n\n\n# 440 - 053a467 changelog\n\n## [0.15.3](https://github.com/vuejs/vite/compare/v0.15.2...v0.15.3) (2020-05-14)\n\n### Bug Fixes\n\n- 依赖优化中，tailwindcss将不被包括在名单 ([fc7b978](https://github.com/vuejs/vite/commit/fc7b978ac334422e919ad026b800a674cbf3d875)), closes [#146](https://github.com/vuejs/vite/issues/146)\n\n### Features\n\n- 添加用于自定义**依赖优化**行为的`optimizeDeps`选项 ([57c1b66](https://github.com/vuejs/vite/commit/57c1b6691c06408cc56b4625e3245ed2de32b317))\n\n"
  },
  {
    "path": "441-450/441-450.md",
    "content": "# 441 - 5769749 v0.15.3\n\nrelease `vite` v0.15.3\n\n\n\n# 442 - d1bdf5a `vite:resolve`插件，当alias不一样时才会调用resolve\n\nmsg: 修复`vite:resolve`与` node-resolve`插件公用的情况下出现的无限循环`resolve`问题。\n\n**node-resolve源码分析：**\n\n`vite:resolve`: 我处理不了这个id，交给其他人处理了。\n\n` node-resolve`: 我找到了这个id的入口了，交给其他人去处理了，我会等待你们的结果，检测你们的`external`，再进行返回。\n\n`vite:resolve`: 我处理不了这个id，交给其他人处理了。\n\n....\n\n重复下去... 一直没有人处理。\n\n**改动后：**\n\n`vite:resolve`: alias和原本id相同，其他插件按照原本流程处理吧。\n\n` node-resolve`: 我找到了这个id的入口了，交给其他人去处理了，我会等待你们的结果，检测你们的`external`，再进行返回。\n\nrollup表示没有人处理这个id，所以回归到` node-resolve`，返回id。\n\n> rollup帮助我们避免了这个死循环，id本身并没有任何改变，所以我们没有察觉（然鹅速度非常慢）。\n\n```typescript\nexport const createBuildResolvePlugin = (\n  root: string,\n  resolver: InternalResolver\n): Plugin => {\n  return {\n    name: 'vite:resolve',\n    async resolveId(id, importer) {\n      const original = id\n      id = resolver.alias(id) || id\n        \n      // ...\n        \n      // fallback to node-resolve becuase alias\n      if (id !== original) {\n        const resolved = this.resolve(id, importer, { skipSelf: true })\n        return resolved || { id }\n      }\n    },\n    load(id: string) {\n    }\n  }\n}\n```\n\n\n\n# 443 - 84cff52 确保vue被安装\n\n`node/utils/resolveVue.ts`: `isLocal`的判断仅仅是检测依赖上有没有vue包，并不是检测`node_modules`能否寻找到vue包，现在为了严谨性，调用`resolveFrom`看看是否能引入该包，否则`isLocal`为`false`\n\n\n\n# 444 - bdc7e70 添加`shouldPreload`在可配置选项中，可预加载非主`chunk`资源 [#144](https://github.com/vitejs/vite/pull/144)\n\n构建完毕后，调用renderIndex -> (非主chunk) ->  shouldPreload(chunk) 验证通过后，`<link red=\"modulepreload\" href=\"${filename}\" />`植入标签到html。\n\n```typescript\n# node/config.ts\nexport interface BuildConfig extends SharedConfig {\n  // ...\n  /**\n   * 是否把link rel=modulepreload注入到index.html中\n   */\n  shouldPreload?: (chunk: OutputChunk) => boolean\n}\n\n# build/buildPluginHtml.ts\nconst injectPreload = (html: string, filename: string) => {\n  filename = isExternalUrl(filename)\n    ? filename\n    : `${publicBasePath}${path.posix.join(assetsDir, filename)}`\n  const tag = `<link rel=\"modulepreload\" href=\"${filename}\" />`\n  if (/<\\/head>/.test(html)) {\n    return html.replace(/<\\/head>/, `${tag}\\n</head>`)\n  } else {\n    return tag + '\\n' + html\n  }\n}\n\nconst renderIndex = (\n    bundleOutput: RollupOutput['output'],\n    cssFileName: string\n  ) => {\n    // inject css link\n    processedHtml = injectCSS(processedHtml, cssFileName)\n    // inject js entry chunks\n    for (const chunk of bundleOutput) {\n      if (chunk.type === 'chunk') {\n        if (chunk.isEntry) {\n          processedHtml = injectScript(processedHtml, chunk.fileName)\n        } else if (shouldPreload && shouldPreload(chunk)) {\n          processedHtml = injectPreload(processedHtml, chunk.fileName)\n        }\n      }\n    }\n    return processedHtml\n  }\n```\n\n**sventschui**: 此PR添加了一个名为`shouldlpreload`的可选配置选项，该选项接收`rollup OutputChunk`作为function的参数，并返回一个布尔值，指示是否向`index.html`添加`<link rel=“modulerepload”href=“…”/>`。\n\n欢迎反馈。也许更通用的解决方案是提供添加html处理插件的功能。\n\n**sventschui**: Fixed typo & rebased.希望现在一切都好\n\n**yyx**: 我想知道是否有方法可以自动执行此操作-例如，如果入口块需要一个公共块，那么它应该自动预加载。我想我也在试图弄清楚什么时候真的需要这个方法，或者我们应该提供一个更通用的HTML处理选项。目前Vite只支持单个入口（除非通过`rollupInputOptions`显式配置），因此只有在使用动`dynamic imports`分离代码时，才会出现单独的块，否则它们将始终位于主块中。您是否有更具体的案例计划使用此功能？\n\n> 尤大目前意思是vite不会代码分割，除非自己通过rollup输出代码，所以输出的只会输出一个主chunk，sventschui提出的shouldPreload(chunk) ，将会是一个主`chunk` + `dynamicimported chunk`。\n\n**sventschui**: 用例是预加载验证后所需的块 (i.e. [https://prelease.netlify.app](https://prelease.netlify.app/) 提供了一个 \"Authenticate with GitHub\"按钮作为未验证时的唯一入口点，认证后会加载`@urql/preact` + `graphql`，大约35KB)。\n\n> **yyx**: 我想知道是否有方法可以自动执行此操作-例如，如果入口块需要一个公共块，那么它应该自动预加载。\n\n这是个好主意。但有一件事是，这仅适用于一个级别（即`main chunk` -> `common chunk`）。在我的情况下，有两个级别（即`main chunk` -> `dynamicimported chunk` -> `common chunk`）我希望两者都被预加载。\n\n> Chrome在50版中添加了对<link rel =“ preload”>的支持，这是一种在浏览器需要资源之前以声明方式提前请求资源的方法。\n>\n> isEntry chunk就是主chunk。\n>\n> common chunk公共模块。\n>\n> dynamicimported chunk异步块。\n>\n> 后续会详细讲解，因为我能看出尤大蠢蠢欲动。\n>\n> 支持`publicBasePath`\n\n\n\n# 445 - 00f4a83 强制esbuild服务输出ES2019 [#155](https://github.com/vitejs/vite/pull/155)\n\n**Dykam**: Rollup 似乎不支持可选链（foo?.bar）。通过将 esbuild 目标降低到 ES2019 来解决此问题。\n这纯粹是一个建议，我知道可能会有意想不到的后果。我也可以想到你可能只想对rollup执行此操作。\n\n![1](1.png)\n\nrollup不支持可选链（Es2020）。\n\n> The reason Acorn hasn't landed optional chaining is due to the estree block over how to implement the Node structure. RollupJS doesn't publicly expose the Node interface so I don't believe Rollup as a project should be slowed down by the lack of consensus building work being done here, if it doesn't actually affect Rollup.\n>\n> Acorn 没有上这个功能是因为estree相关规范还没有定下来。\n\nesbuild是用来转ts，(在renderChunk的时候压缩代码，但是传递给esbuild的只有minify字段)，把目标输出为es2019就可以了。\n\n[rollup-3582](https://github.com/rollup/rollup/pull/3582): 支持可选链。\n\n[#525](https://github.com/vitejs/vite/pull/525/files)升级后，将支持可选链。\n\n> 这个修复也是有缺陷的，不能在js里面用。\n>\n> 同时发现esbuild不支持转换async，https://esbuild.github.io/content-types/#es5，不支持转换es5，那就得交给rollup了。\n\n\n\n# 446 - f6085fc 修复单词拼写错误 [#167](https://github.com/vitejs/vite/pull/167)\n\n修复变量名称拼写错误\n\n\n\n# 447 - 1f3a9b1 [#169](https://github.com/vitejs/vite/pull/169) 把`@tailwindcss/ui`在依赖优化名单中去除\n\n**yyx**: 仅供参考，这也可以通过 `vite.config.js` 中的 `optimizeDeps.exclude` 手动添加。\n\n**posva**: 是的，我正在使用它 ([#168 (comment)](https://github.com/vitejs/vite/issues/168#issuecomment-629768528)).。你认为有没有办法提前以某种方式通知用户？看来这个问题很容易发生，内存错误也很混乱，所以我认为围绕它改进真的很值得。\n\n\n\n# 448 - 59da38c [#170](https://github.com/vitejs/vite/pull/170) 支持accept hmr api能被多次调用\n\n该坑可以在[312](https://github.com/Kingbultsea/vite-analysis/blob/ca743cb71f4123201f705ca2c73e2d5483801beb/311-320/311-320.md#312---0708279-%E9%87%8D%E6%9E%8497%E4%B8%94%E5%85%A8%E5%B1%80%E6%B3%A8%E5%86%8Cimporter%E4%B8%8Eimportee%E5%85%B3%E7%B3%BB%E5%8D%B3%E8%AE%BE%E7%BD%AEimportermap)中查看，曾经提出过。\n\n改动部分：\n\n`client/client.ts`: `hot.accept`现在`key`为`id`，`value`存放了`{id: string, callbacks:{deps: string | string[], fn: (modules: object | object[]) => void}[] }`，解决覆盖问题；`js-update`触发`updateModule`（详 **改动一**）\n\n> `js-update`能用到的就只有hmr api。\n\n### 改动一 `updateModule`\n\n现在A为引入了`hmr api`的模块，A调用`hot.accept(['./B.js', './C.js'], fn1)`。\n\nB改动，触发`js-update`。\n\n客户端`const newMod = await import('B和C.js路径')`。\n\n调用`fn1(newMod)`。\n\n#### 为什么自身引入要清空？\n\n[动态import](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import#%E5%8A%A8%E6%80%81import)，查看第三点，自身引入是有副作用的，也就是说在更新的A模块，会调用accept进行注册，旧的A模块需要让他不起作用。\n\n> B改动，C也会跟着被引入调用callback。\n>\n> 这个PR可以说是去除了相当多的坑，感谢[underfin](https://github.com/underfin) 。\n\n```typescript\ninterface HotModule {\n  id: string\n  callbacks: HotCallback[]\n}\n\ninterface HotCallback {\n  deps: string | string[]\n  fn: (modules: object | object[]) => void\n}\n\nasync function updateModule(\n  id: string,\n  changedPath: string,\n  timestamp: string\n) {\n  const mod = jsHotModuleMap.get(id)\n  if (!mod) {\n    console.error(\n      `[vite] got js update notification but no client callback was registered. Something is wrong.`\n    )\n    return\n  }\n\n  const moduleMap = new Map()\n  const isSelfUpdate = id === changedPath\n\n  // make sure we only import each dep once\n  const modulesToUpdate = new Set<string>()\n  if (isSelfUpdate) {\n    // self update - only update self\n    modulesToUpdate.add(id)\n  } else {\n    // dep update\n    for (const { deps } of mod.callbacks) {\n      if (Array.isArray(deps)) {\n        deps.forEach((dep) => modulesToUpdate.add(dep))\n      } else if (deps !== id) {\n        // exclude self accept calls\n        modulesToUpdate.add(deps)\n      }\n    }\n  }\n\n  // determine the qualified callbacks before we re-import the modules\n  const callbacks = mod.callbacks.filter(({ deps }) => {\n    return Array.isArray(deps)\n      ? deps.some((dep) => modulesToUpdate.has(dep))\n      : modulesToUpdate.has(deps)\n  })\n  // reset callbacks on self update since they are going to be registered again\n  if (isSelfUpdate) {\n    mod.callbacks = []\n  }\n\n  await Promise.all(\n    Array.from(modulesToUpdate).map(async (dep) => {\n      debugger\n      const disposer = jsDisposeMap.get(dep)\n      if (disposer) await disposer()\n      const newMod = await import(dep + `?t=${timestamp}`)\n      moduleMap.set(dep, newMod)\n    })\n  )\n\n  for (const { deps, fn } of callbacks) {\n    if (Array.isArray(deps)) {\n      fn(deps.map((dep) => moduleMap.get(dep)))\n    } else {\n      fn(moduleMap.get(deps))\n    }\n  }\n\n  console.log(`[vite]: js module hot updated: `, id)\n}\n```\n\n\n\n# 449 - b8f6b5a cva 更新vue模板 [#151](https://github.com/vitejs/vite/pull/151)\n\n添加icon，和vue-cli生成的模板保持一致。\n\n\n\n# 450 - 9b0f742 chore 简化cli address log\n\n没什么改动，就是把没必要声明的类型去掉了。\n\n![2](2.png)\n\n"
  },
  {
    "path": "451-460/451-460.md",
    "content": "# 451 - d3e08d2 `bump vue` & 补上忘了添加进去的`mime-types`依赖\n\n`mime-types`被`puppeteer`作为了依赖，可以正常使用，所以尤大并没有察觉。\n\n\n\n# 452 - c40978f 简化`ws`代码 [#180](https://github.com/vitejs/vite/pull/180)\n\n`ws`可以直接调用`clients`来获取正在连接的`ws`服务，同时可以通过`client.readyState`知道链接是否有效。\n\n```typescript\nconst wss = new WebSocket.Server({ server })\n\nwss.clients.forEach((client) => {\n      if (client.readyState === WebSocket.OPEN) {\n        client.send(stringified)\n      }\n    })\n```\n\n\n\n# 453 - 0cc57c8 为`css` & 主`chunk`添加hash\n\n资源生成文件夹从`assets`更改为`_assets`。\n\n现在为`css`、`dynamicimported chunk`和主`chunk`([444](https://github.com/Kingbultsea/vite-analysis/blob/b6617741425483652faf7447e5424fffa9d71b16/441-450/441-450.md#444---bdc7e70-%E6%B7%BB%E5%8A%A0shouldpreload%E5%9C%A8%E5%8F%AF%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9%E4%B8%AD%E5%8F%AF%E9%A2%84%E5%8A%A0%E8%BD%BD%E9%9D%9E%E4%B8%BBchunk%E8%B5%84%E6%BA%90-144) - 底部解释)添加名称hash。\n\n[`output.chunkFileNames`](https://rollup.docschina.org/guide/en/#outputchunkfilenames): `dynamicimported chunk`，异步`chunk`。\n\n[`output.entryFileNames`](https://rollup.docschina.org/guide/en/#outputentryfilenames): 入口chunk，主chunk。\n\n```typescript\nconst { output } = await bundle.generate({\n    format: 'es',\n    sourcemap,\n    entryFileNames: `[name].[hash].js`,\n    chunkFileNames: `common.[hash].js`,\n    ...rollupOutputOptions\n  })\n\n// 尤大是不是弄反了呢？认为异步chunk，以文件名称是最好的。\n// 如下图的TestAsync.388b3ea8.js\n```\n\n![1](1.png)\n\n>静态资源的文件hash已经在`buildPluginAssets.ts`被添加。\n>\n>`js`是`rollup`添加的`hash`，是内容`hash`。\n\n\n\n# 454 - d16f567 fix [#160](https://github.com/vitejs/vite/issues/160) 支持`.html`路由跳转\n\n不再写死`./index.html`。\n\n> 构建的时候是写死... 我就看到时候谁反馈了。\n>\n> 现在只解决了server。\n>\n> [426](https://github.com/Kingbultsea/vite-analysis/blob/da8c66f4dd50db3014bb9d01d6e5210fa0848646/421-430/421-430.md#426---a6a76a7-indexhtml%E6%94%AF%E6%8C%81hmr%E8%B0%83%E7%94%A8%E8%A7%A6%E5%8F%91full-reload)有提到。\n>\n> （通过rollup修改入口是可以，但是`createBuildHtmlPlugin`会无法读取到文件，也是行不通的）\n\n\n\n# 455 - 89ac245 fix [#161](https://github.com/vitejs/vite/issues/161) 用户有可能在`index.html`有注释\n\n一开始我看这个问题也是懵逼... 构建和serve完全没有关系的，怎么161描述的情况就是说构建后，启动serve服务，会被注释掉`dev`的代码...\n\n实际触发的原因是`vite`注入`dev`到`html`，他会在第一个匹配到的`<script>`标签上方添加`dev`代码。\n\n```typescript\n<body>\n  <div id=\"app\"></div>\n  <!-- <script type=\"module\">\n    console.log(123)\n  </script> -->\n  <script type=\"module\" src=\"/main.js\"></script>\n</body>\n```\n\n\n\n![2](2.png)\n\n现在修改为在`html`顶部注入`dev`就可以解决了。\n\n![3](3.png)\n\n\n\n# 456 - f6a21b2 [#176](https://github.com/vitejs/vite/issues/176) 修复组件`onMounted`无法第一时间获取style的问题\n\n原因是`css`是通过注入`<link/>`来达到样式修改，是一个异步的过程，而组件`onMounted`是同步的。\n\n现在需要把样式改为同步的。\n\n**注意了`style-update` & `vue-style-update` 变动了**，现在会同步等待内容再插入`<style/>`标签。\n\n改动部分：\n\n- `client/client.ts`: `updateStyle`传入参数从路径改为内容； 等待`import`获取style内容，`<link/>`标签更改为`<style/>`标签，其余不变\n- `server/serverPluginCss.ts`: `updateStyle`传入参数从路径改为内容\n- `server/serverPluginVue.ts`: 对待`css`注入，新增`import`获取内容，再调用`updateStyle`，和`client.ts`的改动是一致的\n- `server/serverPluginHmr.ts`: `file.endsWidth('.module.css')`不再进行`handleJSReload`，优化性能，减少一次`HMR`，这是一个操作问题（详 **改动四**）\n\n> 对于SFC组件的`src`资源`hmr`，仅由`serverPluginVue.ts`处理。\n>\n> 配置了`config,transforms`转换的`css`文件，`hmr`会触发`handleJSReload`（`serverPluginHmr.ts`） & `style-update`（`serverPluginCss.ts`）事件，仅经过`serverPluginHmr.ts` & `serverPluginCss.ts`处理。\n\n### 改动四\n\n假设你修改了`.module.css`，你新增了一个`class`，你会希望进行`handleJSReload`让你的SFC触发`vue-reload`吗？\n\n**不会，这不合理**。\n\n因为按照`module`的特性，你必然是会进行一次SFC的修改去引用你新增的`class`，这次改动有效减少一次`handleJSReload`，如果是SFC组件即`vue-reload`。\n\n\n\n# 457 - 10a922d chore `css modules` hash调整\n\n改动部分：\n\n- `node/build/buildPluginCss.ts`:  `hash id`（**改动一**）\n- `node/build/index.ts`: 配置`rollup-plugin-vue`，新增`cssModulesOptions`（**改动二**）\n- `node/server/serverPluginVue.ts`: 新增`modulesOptions.generateScopedName`（**改动三**）\n\n### 改动一\n\n新增`generateScopedName`，`hash id`，即`import id` `requestId`\n\n```typescript\nrequire('postcss-modules')({\n  generateScopedName: `[local]_${hash_sum(id)}`,\n  getJSON(_: string, json: Record<string, string>) {\n    modules = json\n  }\n})\n```\n\n### 改动二\n\n即`postcss-modules`配置。\n\nhttps://github.com/madyankin/postcss-modules#usage\n\n```typescript\n// vue\n    require('rollup-plugin-vue')({\n      cssModulesOptions: {\n        generateScopedName: (local: string, filename: string) =>\n          `${local}_${hash_sum(filename)}`\n      }\n    })\n```\n\n### 改动三\n\n本质也是`postcss-modules`:\n\n```typescript\nfunction compileStyleAsync(options) {\n    return doCompileStyle({ ...options, isAsync: true });\n}\nfunction doCompileStyle({ modulesOptions }) {\n    plugins.push(require('postcss-modules')({\n            ...modulesOptions,\n            getJSON: (_cssFileName, json) => {\n                cssModules = json;\n            }\n        })\n}\n```\n\n> `id`为 `hash_sum(publicPath)`\n\n\n\n# 458 - 580ace9 changelog\n\n## [0.15.4](https://github.com/vuejs/vite/compare/v0.15.3...v0.15.4) (2020-05-19)\n\n### Bug Fixes\n\n- resolveVue 中会确保vue依赖被安装 ([84cff52](https://github.com/vuejs/vite/commit/84cff5282a56369eeea360cf001e398a2d25dd56))\n- 支持`.html`路由跳转 ([d16f567](https://github.com/vuejs/vite/commit/d16f567ef2b6fb7b764b5be4402dd81ba7061596)), closes [#160](https://github.com/vuejs/vite/issues/160)\n- 修复`vite:resolve`与` node-resolve`插件公用的情况下出现的无限循环`resolve`问题。 ([d1bdf5a](https://github.com/vuejs/vite/commit/d1bdf5a07fdae032a69987ac238bc0d68881b3f2))\n- 强制`esbuild`输出ES2019 ([#155](https://github.com/vuejs/vite/issues/155)) ([00f4a83](https://github.com/vuejs/vite/commit/00f4a8319fcc79ebdf939ecb5aea990c46690fd8))\n- `tailwindui`在依赖优化中被ignore ([#169](https://github.com/vuejs/vite/issues/169)) ([1f3a9b1](https://github.com/vuejs/vite/commit/1f3a9b1adc73e2569425e7ab4c129734d59bdfcd)), closes [#168](https://github.com/vuejs/vite/issues/168)\n- 修复dev的代码在html中注入的位置问题 ([89ac245](https://github.com/vuejs/vite/commit/89ac24552f5cf644d416230058293d0d0d8eef5f)), closes [#161](https://github.com/vuejs/vite/issues/161)\n- 修复组件`onMounted`无法第一时间获取style的问题 ([#176](https://github.com/vuejs/vite/issues/176)) ([f6a21b2](https://github.com/vuejs/vite/commit/f6a21b268b262ddbdd585288e599fed2b3a41ec6)), closes [#175](https://github.com/vuejs/vite/issues/175)\n- 支持`accept hmr api`能被多次调用 ([#170](https://github.com/vuejs/vite/issues/170)) ([59da38c](https://github.com/vuejs/vite/commit/59da38c185b428a178d320b8bd5187b34bd942aa)), closes [#158](https://github.com/vuejs/vite/issues/158)\n\n### Features\n\n- 添加`shouldPreload`在可配置选项中，可预加载非主chunk资源 ([#144](https://github.com/vuejs/vite/issues/144)) ([bdc7e70](https://github.com/vuejs/vite/commit/bdc7e70499916d7668d40a84c3f726ab50fbce9a))\n- 为`css` & 主`chunk`添加hash ([0cc57c8](https://github.com/vuejs/vite/commit/0cc57c80d92dabb024a18d81d92a1dabe8eda702))\n\n\n\n# 459 - 0a4ed59 `vite v0.15.4`\n\nrelease `vite v0.15.4`\n\n\n\n# 460 - a0fb9fb `cva v1.5.1`\n\nrelease `cva v1.5.1`\n\n"
  },
  {
    "path": "461-470/461-470.md",
    "content": "# 461 - 0e68cc1 在没有`esm`依赖的时候输出更多有用的信息\n\n改动部分:\n\n- `node/depOptimizer.ts`: 新增`commonJSWhitelist`，调用`resolveNodeModuleEntry`判断`package.json`是否存在，不存在则不在依赖优化名单；去除对`cjs`的依赖优化，现在变更为两种（详 **改动一**）\n- `node/resolver.ts`:`resolveNodeModuleEntry`将返回`[入口, package.json]`信息\n\n> 我所指的不在依赖优化名单，但是在依赖中的包，会被设置为`external`（`rollup`）\n\n### 改动一\n\n用户可以配置`config.optimizeDeps`添加白名单;添加白名单的包，将会强行被列入依赖优化名单中。\n\n```typescript\nexport interface DepOptimizationOptions {\n  /**\n   * Only optimize explicitly listed dependencies.\n   */\n  include?: string[]\n  /**\n   * Do not optimize these dependencies.\n   */\n  exclude?: string[]\n  /**\n   * Explicitly allow these CommonJS deps to be bundled.\n   */\n  commonJSWhitelist?: string[]\n  /**\n   * Automatically run `vite optimize` on server start?\n   * @default true\n   */\n  auto?: boolean\n}\n```\n\n现在变更为对两种类型的包进行**依赖优化**：\n\n```typescript\n// 要优化的依赖。目标是预捆绑以下类型的依赖:\n// 1. 拥有引入相对路径文件的import (e.g. lodash-es, lit-html)\n//    如 export { default as add } from './add.js';\n// 2. Has imports to bare modules that are not in the project's own deps\n//    (i.e. esm that imports its own dependencies, e.g. styled-components)\n```\n\n> 如果检测到你的依赖，没有module信息，没有exports句子，没有`!/export\\s+\\*\\s+from/.test(content)`，则被视为`cjs`包。\n\n### 错误信息\n\n如果没有能够依赖优化的包，但是有`cjs`的包，将输出错误：\n\n这是一个`cjs`的包，对`cjs`的支持不是很友好（`package.json module`缺失），如果你不是在浏览器中使用这个包，你可以把他移动到`devDependencies`或者通过`optimizeDeps.exclude`删除，如果你坚持要在浏览器中使用，你可以通过`optimizeDeps.commonJSWhitelist`它添加在白名单中，但是这有导致打包可能会出现错误，你应该考虑选择一些更先进提供`esm`的包。\n\n> 现在都把`cjs`给直接删除了，我觉得依赖优化只剩下一个减少网络请求的功能了\n\n```typescript\nconsole.error(\n        chalk.yellow(\n          `[vite] The following dependencies seem to be CommonJS modules that\\n` +\n            `do not provide ESM-friendly file formats:\\n\\n  ` +\n            cjsDeps.map((dep) => chalk.magenta(dep)).join(`\\n  `) +\n            `\\n` +\n            `\\n- If you are not using them in browser code, you can move them\\n` +\n            `to devDependencies or exclude them from this check by adding\\n` +\n            `them to ${chalk.cyan(\n              `optimizeDeps.exclude`\n            )} in vue.config.js.\\n` +\n            `\\n- If you do intend to use them in the browser, you can try adding\\n` +\n            `them to ${chalk.cyan(\n              `optimizeDeps.commonJSWhitelist`\n            )} in vue.config.js but they\\n` +\n            `may fail to bundle or work properly. Consider choosing more modern\\n` +\n            `alternatives that provide ES module build formts.`\n        )\n      )\n```\n\n\n\n# 462 - 62a720a 支持`import css`依赖优化\n\n`node/depOptimizer.ts`新增`createBuildCssPlugin`。\n\n```typescript\nconst warningIgnoreList = [`CIRCULAR_DEPENDENCY`, `THIS_IS_UNDEFINED`]\n    const bundle = await rollup.rollup({\n      input,\n      external: preservedDeps,\n      treeshake: { moduleSideEffects: 'no-external' },\n      onwarn(warning, warn) {\n        if (!warningIgnoreList.includes(warning.code!)) {\n          warn(warning)\n        }\n      },\n      ...config.rollupInputOptions,\n      plugins: [\n        ...(await createBaseRollupPlugins(root, resolver, config)),\n        createBuildCssPlugin(root, '/', 'assets')\n      ]\n    })\n```\n\n> 也就是我们可以引入`.css`包，比如`ant`/`SFC vue`这种组件。\n\n\n\n# 463 - 9e5ca2b changelog\n\n## [0.15.5](https://github.com/vuejs/vite/compare/v0.15.4...v0.15.5) (2020-05-19)\n\n### Features\n\n- 非`esm`依赖项的更详细警告 ([0e68cc1](https://github.com/vuejs/vite/commit/0e68cc18bc0ad60b6d469b41da66d5bfa7f86109))\n- 在捆绑的依赖中支持`css`导入 ([62a720a](https://github.com/vuejs/vite/commit/62a720a8f23b958f91d5f6ae79989535b356cec6))\n\n\n\n# 464 - 3355bc8 `vite v0.15.0`\n\nrelease `vite v0.15.0`\n\n\n\n# 465 - dafaccb 支持proxy转发请求\n\n改动部分：\n\n- 新增`node/server/serverPluginProxy.ts`: 一个内置的代理`vite`服务插件，通过`koa-proxies`传递配置。（详 **新增一**）\n\n- 新增`koa-proxies`包，用法（详 **新增二**）\n- `node/config.ts`: 新增proxy（详 **新增三**）\n\n### 新增一\n\n实际调用`app.use(proxy(path, opts))`。\n\n用例：\n\n```typescript\nmodule.exports = {\n   proxy: {\n     proxy: {\n        // string shorthand\n       '/foo': 'http://localhost:4567/foo',\n        // with options\n       '/api': {\n         target: 'http://jsonplaceholder.typicode.com',\n         changeOrigin: true,\n         rewrite: path => path.replace(/^\\/api/, '')\n       }\n     }\n   }\n}\n```\n\n源代码：\n\n```typescript\nimport { ServerPlugin } from '.'\nimport { URL } from 'url'\n\nexport const proxyPlugin: ServerPlugin = ({ app, config }) => {\n  if (!config.proxy) {\n    return\n  }\n\n  const debug = require('debug')('vite:proxy')\n  const proxy = require('koa-proxies')\n  const options = config.proxy\n  Object.keys(options).forEach((path) => {\n    let opts = options[path]\n    if (typeof opts === 'string') {\n      opts = { target: opts }\n    }\n    opts.logs = (ctx, target) => {\n      debug(\n        `${ctx.req.method} ${(ctx.req as any).oldPath} proxy to -> ${new URL(\n          ctx.req.url!,\n          target\n        )}`\n      )\n    }\n    app.use(proxy(path, opts))\n  })\n}\n```\n\n### 新增二\n\n一个转发请求的中间件。\n\n```typescript\n{\n    target : <url string to be parsed with the url module>\n    forward: <url string to be parsed with the url module>\n    agent  : <object to be passed to http(s).request>\n    ssl    : <object to be passed to https.createServer()>\n    ws     : <true/false, if you want to proxy websockets>\n    xfwd   : <true/false, adds x-forward headers>\n    secure : <true/false, verify SSL certificate>\n    toProxy: <true/false, explicitly specify if we are proxying to another proxy>\n    prependPath: <true/false, Default: true - specify whether you want to prepend the target's path to the proxy path>\n    ignorePath: <true/false, Default: false - specify whether you want to ignore the proxy path of the incoming request>\n    localAddress : <Local interface string to bind for outgoing connections>\n    changeOrigin: <true/false, Default: false - changes the origin of the host header to the target URL>\n    preserveHeaderKeyCase: <true/false, Default: false - specify whether you want to keep letter case of response header key >\n    auth   : Basic authentication i.e. 'user:password' to compute an Authorization header.\n    hostRewrite: rewrites the location hostname on (201/301/302/307/308) redirects, Default: null.\n    autoRewrite: rewrites the location host/port on (201/301/302/307/308) redirects based on requested host/port. Default: false.\n    protocolRewrite: rewrites the location protocol on (201/301/302/307/308) redirects to 'http' or 'https'. Default: null.\n  \n}\n```\n\n[选项列表](https://github.com/http-party/node-http-proxy/blob/master/lib/http-proxy.js#L26-L42)。\n\n[koa-proxies支持websocket](https://www.ithere.net/article/539)。\n\n### 新增三\n\n用户可以通过`config.proxy配置`。\n\n```typescript\nexport interface ServerConfig extends SharedConfig {\n  /**\n   * Configure custom proxy rules for the dev server. Uses\n   * [`koa-proxies`](https://github.com/vagusX/koa-proxies) which in turn uses\n   * [`http-proxy`](https://github.com/http-party/node-http-proxy). Each key can\n   * be a path Full options\n   * [here](https://github.com/http-party/node-http-proxy#options).\n   *\n   * Example `vite.config.js`:\n   * ``` js\n   * module.exports = {\n   *   proxy: {\n   *     proxy: {\n   *       // string shorthand\n   *       '/foo': 'http://localhost:4567/foo',\n   *       // with options\n   *       '/api': {\n   *         target: 'http://jsonplaceholder.typicode.com',\n   *         changeOrigin: true,\n   *         rewrite: path => path.replace(/^\\/api/, '')\n   *       }\n   *     }\n   *   }\n   * }\n   * ```\n   */\n  proxy?: Record<string, string | IKoaProxiesOptions>\n}\n```\n\n\n\n# 466 - 45fde5b fix [#188](https://github.com/vitejs/vite/issues/188) 防止`ServerConfig.plugin`被覆盖\n\n用户想定义`vite`服务的插件，但显示错误（包括类型），原因是`UserConfig.plugins`与`ServerConfig.plugins`名称定义相同了。\n\n名称更改：`ServerConfig.plugin` -> `ServerConfig.configureServer`\n\n用户定义的plugins:\n\n```typescript\n// 用户定义的plugins\nexport interface UserConfig extends BuildConfig, ServerConfig {\n  plugins?: Plugin[]\n    // configureServer?: ServerPlugin\n}\n\nexport interface Plugin\n  extends Pick<\n    UserConfig,\n    | 'alias'\n    | 'transforms'\n    | 'resolvers'\n    | 'configureServer'\n    | 'vueCompilerOptions'\n    | 'rollupInputOptions'\n    | 'rollupOutputOptions'\n  > {}\n```\n\n`vite`服务的plugin:\n\n```typescript\n// vite 服务的plugin\nexport interface ServerConfig extends SharedConfig {\n  /**\n   * Configure custom proxy rules for the dev server. Uses\n   * [`koa-proxies`](https://github.com/vagusX/koa-proxies) which in turn uses\n   * [`http-proxy`](https://github.com/http-party/node-http-proxy). Each key can\n   * be a path Full options\n   * [here](https://github.com/http-party/node-http-proxy#options).\n   *\n   * Example `vite.config.js`:\n   * ``` js\n   * module.exports = {\n   *   proxy: {\n   *     proxy: {\n   *       // string shorthand\n   *       '/foo': 'http://localhost:4567/foo',\n   *       // with options\n   *       '/api': {\n   *         target: 'http://jsonplaceholder.typicode.com',\n   *         changeOrigin: true,\n   *         rewrite: path => path.replace(/^\\/api/, '')\n   *       }\n   *     }\n   *   }\n   * }\n   * ```\n   */\n  proxy?: Record<string, string | IKoaProxiesOptions>\n  /**\n   * Whether to use a Service Worker to cache served code. This can greatly\n   * improve full page reload performance, but requires a Service Worker\n   * update + reload on each server restart.\n   *\n   * @default false\n   */\n  serviceWorker?: boolean\n  configureServer?: ServerPlugin | ServerPlugin[]\n}  \n\nexport type ServerPlugin = (ctx: ServerPluginContext) => void\nexport interface ServerPluginContext {\n  root: string\n  app: Koa\n  server: Server\n  watcher: HMRWatcher\n  resolver: InternalResolver\n  config: ServerConfig & { __path?: string }\n}\n```\n\n\n\n# 467 - 36dba85 bump `rollup-plugin-vue` fix [#189](https://github.com/vitejs/vite/issues/189)\n\n构建时，当`module`出现的位置不在第一位的时候会报错。修复`rollup-plugin-vue`插件。\n\n`rollup-plugin-vue`: [51fdbc](https://github.com/vuejs/rollup-plugin-vue/commit/51fdbcc67da93707aa001eede4fd6894d4a5851b)，转换为`js`代码`const cssModules = script.__cssModules = {}`没有换行，导致rollup报错。\n\n现更改为`\\nconst cssModules = script.__cssModules = {}`。\n\n```css\n<style>\n  .main {\n    background: blue;\n  }\n</style>\n\n<style module> <!-- 转换不换行 就会导致js代码不正常 -->\n  #app {\n    background: red;\n  }\n</style>\n```\n\n\n\n# 468 - e483fc6 serve端修复相对路径引入资源的bug\n\n改动部分：\n\n- `node/build/buildPluginCss.ts`: 重构`css`收集`url()`的逻辑，也可以当作是封装`css`改写的逻辑为`rewriteCssUrls`到`cssUtils.ts`中\n\n- `node/server/serverPluginCss.ts`: 调用`rewriteCssUrls`，改写`url()`的路径，位置`ctx.path`，即浏览器请求的文件路径。\n\n- `node/server/serverPluginVue.ts`: 调用`rewriteCssUrls`，改写`url()`的路径，位置`ctx.path`，即浏览器请求的文件路径。\n\n- `node/utils/cssUtils.ts`: `rewriteCssUrls`，一个可以改写`url()`的方法，从`node/build/buildPluginCss.ts`中迁移过来（详 **新增四**）\n\n### 新增四 `rewriteCssUrls`\n\n一个对非`data:`|`外链`类型的`url(资源路径)`资源路径进行改写为正常`publicPath`的方法。传递进来的`replacerOrBase`参数如果是字符串，则调用`path.dirname`获取所在的**文件夹位置**，利用`path.resolve(文件夹位置, 资源路径)`进行合并，得出的结果将改写`url(资源路径)`。\n\n> 用人话说：你的浏览器请求一个SFC组件或者其他脚本，`rewriteCssUrls`将会把`url(XXX)`的XXX改写为资源的真实位置。\n>\n> 注意！！`data:XXX`类型或者`httpXXXX`这些类型都不会进行改写。\n>\n> 这个正则我们可以抄一下~\n\n```typescript\nimport path from 'path'\nimport { asyncReplace } from './transformUtils'\nimport { isExternalUrl } from './pathUtils'\n\nconst urlRE = /(url\\(\\s*['\"]?)([^\"')]+)([\"']?\\s*\\))/\n\ntype Replacer = (url: string) => string | Promise<string>\n\nexport function rewriteCssUrls(\n  css: string,\n  replacerOrBase: string | Replacer\n): Promise<string> {\n  let replacer: Replacer\n  if (typeof replacerOrBase === 'string') {\n    replacer = (rawUrl) => {\n      return path.posix.resolve(path.posix.dirname(replacerOrBase), rawUrl)\n    }\n  } else {\n    replacer = replacerOrBase\n  }\n\n  return asyncReplace(css, urlRE, async (match) => {\n    const [matched, before, rawUrl, after] = match\n    if (isExternalUrl(rawUrl) || rawUrl.startsWith('data:')) {\n      return matched\n    }\n    return before + (await replacer(rawUrl)) + after\n  })\n}\n```\n\n\n\n# 469 - ce83640 changelog\n\n## [0.15.6](https://github.com/vuejs/vite/compare/v0.15.5...v0.15.6) (2020-05-19)\n\n### Bug Fixes\n\n- 修复configureServer选项 ([45fde5b](https://github.com/vuejs/vite/commit/45fde5ba3171c7788535a67a5abc0b171b38e3f1)), closes [#188](https://github.com/vuejs/vite/issues/188)\n- 修复server端css资源相对路径无法解析问题 ([e483fc6](https://github.com/vuejs/vite/commit/e483fc67a16392d15a56001da9a795473d495b8d))\n\n### Features\n\n- 支持api转发代理 ([dafaccb](https://github.com/vuejs/vite/commit/dafaccbe291f8cc1db9716827366ddd418637f40)), closes [#147](https://github.com/vuejs/vite/issues/147)\n\n\n\n# 470 - 3b3cef5 `vite v0.15.6` [ci skip]\n\nrelease v0.15.6\n\n> 跳过测试\n\n"
  },
  {
    "path": "471-480/471-480.md",
    "content": "# 471 - 595a093 chore 删除debugger\n\nundef的hmr accept重构PR忘记删除了`debugger`\n\n\n\n# 472 - ef4fc42 支持无参数直接调用`hot.accept()` & 支持分析`es2020 importMeta`\n\n如在testHmrManual.js调用如下：\n\n`hot.accept() -> hot.accept(\"/testHmrManual.js\", \"/testHmrManual.js\", )`\n\n> 防止错误发生，即防止用户调用`hot.accept()`后项目无法启动，引起更多的issues。\n\n\n\n# 473 - b62af73 更新注释\n\n曾经改动过收集`SFC`文件的集合变量名称。\n\n`vueImports` -> `vueBoundaries`\n\n\n\n# 474 - 5b75f56 `esbuildPlugin`前进行用户`transform`\n\n洋葱模型顺序更新。\n\n```typescript\n// 改动前：\nconst resolvedPlugins = [\n    ...(Array.isArray(configureServer) ? configureServer : [configureServer]),\n    proxyPlugin,\n    serviceWorkerPlugin,\n    hmrPlugin,\n    moduleRewritePlugin,\n    moduleResolvePlugin,\n    vuePlugin,\n    esbuildPlugin,\n    jsonPlugin,\n    cssPlugin,\n    assetPathPlugin,\n    ...(transforms.length ? [createServerTransformPlugin(transforms)] : []),\n    serveStaticPlugin\n  ]\n\n// 改动后：\n  const resolvedPlugins = [\n    ...(Array.isArray(configureServer) ? configureServer : [configureServer]),\n    proxyPlugin,\n    serviceWorkerPlugin,\n    hmrPlugin,\n    moduleRewritePlugin,\n    moduleResolvePlugin,\n    vuePlugin,\n    cssPlugin, // 被提前（因为含有config.transforms）\n    ...(transforms.length ? [createServerTransformPlugin(transforms)] : []), // 被提前\n    esbuildPlugin,\n    jsonPlugin,\n    assetPathPlugin,\n    serveStaticPlugin\n  ]\n```\n\n> 意味着我们可以直接更改ts代码，曾经只能更改由esbuild转换ts后的js代码\n\n\n\n# 475 - ce4032b 传递`path isBuild query`给`transform`\n\n改动部分：\n\n- `node/transform.ts`：新增`transform`传入的参数（详 **改动一**）\n- `node/build/buildPluginCss.ts`：新增参数，对应改动一`t.transform(css, true, true, path, query)`，即代表构建状态、当前路径（requestId）、参数（requestId后的参数，如构建模式下`import from 'a?query=123'`）\n- `node/build/index.ts`：添加备注，用户可以不设置`knowNamedExports`通过升级`@rollup/plugin-commonjs`，这也是之前我无法查阅到相关字段的原因。[传送门](https://github.com/Kingbultsea/vite-analysis/blob/8b8276edfe2c70f04a663de96b73bf202ef41546/431-440/431-440.md#%E5%86%8D%E8%AF%A6%E7%BB%86%E8%B0%88%E8%B0%88%E9%9C%80%E8%A6%81%E6%98%8E%E7%A1%AE%E8%A1%A8%E7%A4%BA%E5%AF%BC%E5%87%BA%E5%91%BD%E5%90%8D%E7%9A%84%E4%BE%8B%E5%AD%90)\n\n> 在`serverPluginCss.ts`的`transform`仅仅起到一个`hmr`的作用，如非`.css`的文件可以走`.css`的`hmr`流程。\n\n### 改动一 新增`transform`的参数给用户\n\n```typescript\nexport interface Transform {\n  /**\n   * @default 'js'\n   */\n  as?: 'js' | 'css'\n  test: (path: string, query: ParsedQuery) => boolean\n  transform: (\n    code: string,\n    /**\n     * Indicates whether this is a request made by js import(), or natively by\n     * the browser (e.g. `<img src=\"...\">`).\n     */\n    isImport: boolean,\n    isBuild: boolean, // 是否构建模式\n    path: string, // 当前路径构建模式： import 'a' -> 那path为'a' 如果为serve模式 则为publicPath,那path为'@modules/a'\n    query: ParsedQuery // 当前路径 import 'a?query=1' -> 那path为'?query=1'\n  ) => string | Promise<string>\n}\n```\n\n\n\n# 476 - c244cc7 resolver类型\n\n`resolver`的`requestToFile`和`fileToRequest`方法为可选，即用户定义`resolver`的时候不强制类型上要定义这两个方法\n\n\n\n# 477 - 86e9fb5 修复依赖优化的缓存路径设置问题\n\n改动部分：\n\n- `cli.ts`：对用户使用`cli`传入相对路径的处理，即`path.isAbsloute() ? argv._[1] : path.resolve(argv._[1])`\n- `node/depOptimizer.ts`：`KNOW_IGNORE_LIST`新增`@pika/react`、`@pika/react-dom`；如依赖包没有`package.json`则跳过依赖优化的代码被移动到顶部，优先检测；`cacheDir`修正为：`path.join(path.dirname(pkgPath), OPTIMIZE_CACHE_DIR)`，即对比原来需要调用`path.dirname`获取文件夹。\n\n\n\n# 478 - 6683bb8 `serverPluginModuleRewrite.ts`当路径拥有拓展名称且非js资源脚本才添加`?import`\n\n`serverPluginModuleRewrite.ts`当路径拥有拓展名称且非js资源脚本才添加`?import`。\n\n> 这意味着用户调用`transform`转换的无拓展名称的文件，不再显示为资源类型的引入，对功能上没有任何影响，仅仅是用户的`transform`有影响，现在已修复正确。\n\n\n\n# 479 - dee2cd6 changelog\n\n# [0.16.0](https://github.com/vuejs/vite/compare/v0.15.5...v0.16.0) (2020-05-19)\n\n### Bug Fixes\n\n- `esbuildPlugin`前进行用户的`transform` 插件([5b75f56](https://github.com/vuejs/vite/commit/5b75f567a5c2e17d48fde0e2df6666f456eccc58))\n- 修复`configureServer`选项名称冲突导致无法设置vite插件的问题([45fde5b](https://github.com/vuejs/vite/commit/45fde5ba3171c7788535a67a5abc0b171b38e3f1)), closes [#188](https://github.com/vuejs/vite/issues/188)\n- 修复开发模式下css下的相对路径资源没有被改写问题([e483fc6](https://github.com/vuejs/vite/commit/e483fc67a16392d15a56001da9a795473d495b8d))\n- 修复依赖优化的缓存路径设置问题 ([86e9fb5](https://github.com/vuejs/vite/commit/86e9fb598ffb702074f8b6153493ca5c6597f671))\n- 当路径拥有拓展名称且非js资源脚本才添加`?import` ([6683bb8](https://github.com/vuejs/vite/commit/6683bb8fb819c6f4935b40f25c2a377037e5ec7d))\n\n### Features\n\n- 内置 server proxy 插件支持代理([dafaccb](https://github.com/vuejs/vite/commit/dafaccbe291f8cc1db9716827366ddd418637f40)), closes [#147](https://github.com/vuejs/vite/issues/147)\n- 传递 isBuild, path & query 参数给用户设置的`transform`方法([ce4032b](https://github.com/vuejs/vite/commit/ce4032b4e12adf2dd4c5480b596d532e0f27d086))\n- 支持`hot.accept()`直接调用([ef4fc42](https://github.com/vuejs/vite/commit/ef4fc42291d9ddb34400da1c93680edfb965530d))\n\n\n\n# 480 - 658b719  `vite v0.16.0`\n\nrelease `vite v0.16.0`\n\n"
  },
  {
    "path": "481-490/481-490.md",
    "content": "# 481 - 0498f73 cva将被分开为单独一个项目\n\nvite不再内嵌`cva`，删除`cva`。\n\n\n\n# 482 - d3a4793 chore 修正readme proxy的用法 [#192](https://github.com/vitejs/vite/pull/192)\n\n```typescript\n// vite.config.js\nmodule.exports = {\n  proxy: {\n    // string shorthand\n    '/foo': 'http://localhost:4567/foo',\n    // with options\n    '/api': {\n      target: 'http://jsonplaceholder.typicode.com',\n      changeOrigin: true,\n      rewrite: path => path.replace(/^\\/api/, '')\n    }\n  }\n}\n```\n\n> 即删除一层`proxy`\n\n\n\n# 483 - f452cf3 chore 修正readme vite插件调用方式 [#199](https://github.com/vitejs/vite/pull/199)\n\n![1](1.png)\n\n\n\n# 484 - c553de2 修正hmr accept self不会回调callBack问题\n\n如果调用`hot.accept(['./自身路径.js', './A.js'], cb)`，包括自身和A文件，则只会import `A.js`回调cb。\n\n修正后两者都能正常调用。\n\n> 记住再次import自身，accept会自动删除旧的accept关系，之前有说，[传送门](https://github.com/Kingbultsea/vite-analysis/blob/8f69f883b9bbfc856a87259789ca350734a8a3b1/441-450/441-450.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%87%AA%E8%BA%AB%E5%BC%95%E5%85%A5%E8%A6%81%E6%B8%85%E7%A9%BA)。\n\n\n\n# 485 - 02e2d94 性能优化，当引入了`hot`才进行改写\n\n`server/serverPluginModuleRewrite.ts`：即`import { hot } from 'vite/hmr'`才会调用`rewriteFileWithHMR`\n\n> import { dispose } from 'vite/hmr'不会进行`rewriteFileWithHMR`的改写。\n\n\n\n# 486 - feb3b6d hmr api 增强warning输出\n\nvite调用hmr api需要添加`__DEV__`判断，否则警告构建后代码将不会被`Treeshaking`掉。\n\n> 本次改动不仅仅是检测最外层`if (__DEV__)`，可以检测任意代码块下的`if (__DEV__)`\n\n\n\n# 487 - 91c4517 修正测试`console.log`输出\n\n修正测试logs。\n\n> 之前hmr api可以多次使用的PR忘了vite的测试会检测`console.log`\n\n\n\n# 488 - f1f5017 changelog\n\n## [0.16.1](https://github.com/vuejs/vite/compare/v0.16.0...v0.16.1) (2020-05-20)\n\n### Bug Fixes\n\n- **hmr:** 修正hmr accept self不会回调`callBack`问题 ([c553de2](https://github.com/vuejs/vite/commit/c553de26234e64ed3cdccd216630a6b5cd49f8f8))\n\n### Features\n\n- 警告有条件的 `hot.accept()` 调用 ([feb3b6d](https://github.com/vuejs/vite/commit/feb3b6d29381c80e6e24a7f629941d1401401cf5))\n\n### Performance Improvements\n\n- 当引入了hmr api的hot才调用`rewriteFileWithHMR` ([02e2d94](https://github.com/vuejs/vite/commit/02e2d94bb77b93103987f6940ca3b11ae30d65b4))\n\n\n\n# 489 - 65c4466 `vite v0.16.1`\n\nrelease `vite v0.16.1`\n\n\n\n# 490 - 2646e29 调整postpublish script\n\n```json\n// 前\n{\n    \"postpublish\": \"git add CHANGELOG.md && git commit -m 'chore: changelog [ci skip]' && git push\"\n}\n\n// 后 删除了 git push\n{\n    \"postpublish\": \"git add CHANGELOG.md && git commit -m 'chore: changelog [ci skip]'\"\n}\n```\n\n> 不想`git push`自动触发。\n>\n> 尤大难道还有其他东西要add？目前没有看到。\n>\n> 推测：修改package.json也会有一个push操作，因为版本号是手动修改的，所以会重复push。\n\n"
  },
  {
    "path": "491-500/491-500.md",
    "content": "# 491 - fb86f0a 重构 [#185](https://github.com/vitejs/vite/pull/185) html逻辑代码被转移到新增的`htmlPlugin`插件当中 & `full-reload`事件准确刷新目标页面\n\n改动部分：\n\n- `client/client.ts`：`full-reload`事件，如果页面路径和当前在浏览器上的路径一致，才执行`location.reload()`（详 **改动一**）\n- `node/server/index.ts`：新增`htmlPlugin`（详 **改动二**）\n- `node/server/serverPluginHmr.ts`：处理`/index.html`的`hmr`代码被移除（转移）\n- `node/server/serverPluginModuleRewrite.ts`：所有`.html`相关的逻辑都被移除（如 注入dev代码 | 缓存 | 提取`src`变为`import`语句 | `importMap`建立联系）\n- `test/test.js`：新增html相关测试。（详 **改动五**）\n- `node/server/serverPluginHtml.ts`：处理`.html`相关逻辑（详 **新增六**）\n\n### 改动一\n\n```typescript\ncase 'full-reload':\n      if (path.endsWith('.html')) {\n        // if html file is edited, only reload the page if the browser is\n        // currently on that page.\n        const pagePath = location.pathname\n        if (\n          pagePath === path ||\n          (pagePath.endsWith('/') && pagePath + 'index.html' === path)\n        ) {\n          location.reload()\n        }\n        return\n      } else {\n        location.reload()\n      }\n```\n\n\n\n如果我们打开了多个web页面，当收到`full-reload`事件，所有页面都会进行一个刷新。\n\n这明显不合理，现在通过判断`full-reload`传递进来的路径来决定哪个页面进行刷新。\n\n> 这只使用与后缀为.html的文件使用\n\n### 改动二\n\n洋葱模型新增`htmlPlugin`：[传送门](https://github.com/Kingbultsea/vite-analysis/blob/f32926641f27f5b7cfd77d4be596a82a2fd32d17/331-340/331-340.md#%E6%B4%8B%E8%91%B1%E6%A8%A1%E5%9E%8B%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F%E6%9B%B4%E6%96%B0-1)（改动不大，不重新整理，可以按照旧版查看）\n\n```typescript\nconst resolvedPlugins = [\n    ...(Array.isArray(configureServer) ? configureServer : [configureServer]),\n    proxyPlugin,\n    serviceWorkerPlugin,\n    hmrPlugin,\n    moduleRewritePlugin,\n    moduleResolvePlugin,\n    vuePlugin,\n    cssPlugin,\n    ...(transforms.length ? [createServerTransformPlugin(transforms)] : []),\n    esbuildPlugin,\n    jsonPlugin,\n    htmlPlugin,\n    assetPathPlugin,\n    serveStaticPlugin\n  ]\n```\n\n### 改动五\n\n```typescript\n// index.html hmr测试 单页面\ntest('hmr (index.html full-reload)', async () => {\n  expect(await getText('title')).toMatch('Vite App')\n  await updateFile('index.html', (content) =>\n    content.replace('Vite App', 'Vite App Test')\n  )\n  await navigateFinish()\n  await expectByPolling(() => getText('title'), 'Vite App Test')\n})\n\n// 特定页面的html文件 hmr测试\ntest('hmr (html full-reload)', async () => {\n  await page.goto('http://localhost:3000/test.html')\n  expect(await getText('title')).toMatch('Vite App')\n  // hmr\n  await updateFile('test.html', (content) =>\n    content.replace('Vite App', 'Vite App Test')\n  )\n  await navigateFinish()\n  await expectByPolling(() => getText('title'), 'Vite App Test')\n})\n```\n\n### 新增六\n\n没有任何变动，都是迁移过来的功能。\n\n```typescript\nimport { rewriteImports, ServerPlugin } from './index'\nimport {\n  debugHmr,\n  ensureMapEntry,\n  hmrClientPublicPath,\n  importerMap\n} from './serverPluginHmr'\nimport { init as initLexer } from 'es-module-lexer'\nimport { cleanUrl, readBody } from '../utils'\nimport LRUCache from 'lru-cache'\nimport path from 'path'\nimport slash from 'slash'\nimport chalk from 'chalk'\n\nconst debug = require('debug')('vite:rewrite')\n\nconst rewriteHtmlPluginCache = new LRUCache({ max: 20 })\n\nexport const htmlPlugin: ServerPlugin = ({\n  root,\n  app,\n  watcher,\n  resolver,\n  config\n}) => {\n  // inject __DEV__ and process.env.NODE_ENV flags\n  // since some ESM builds expect these to be replaced by the bundler\n  const devInjectionCode =\n    `\\n<script>\\n` +\n    `window.__DEV__ = true\\n` +\n    `window.__BASE__ = '/'\\n` +\n    `window.process = { env: { NODE_ENV: 'development' }}\\n` +\n    `</script>` +\n    `\\n<script type=\"module\" src=\"${hmrClientPublicPath}\"></script>\\n`\n\n  const scriptRE = /(<script\\b[^>]*>)([\\s\\S]*?)<\\/script>/gm\n  const srcRE = /\\bsrc=(?:\"([^\"]+)\"|'([^']+)'|([^'\"\\s]+)\\b)/\n\n  async function rewriteHtml(importer: string, html: string) {\n    await initLexer\n    return (\n      devInjectionCode +\n      html!.replace(scriptRE, (matched, openTag, script) => {\n        if (script) {\n          return `${openTag}${rewriteImports(\n            root,\n            script,\n            importer,\n            resolver\n          )}</script>`\n        } else {\n          const srcAttr = openTag.match(srcRE)\n          if (srcAttr) {\n            // register script as a import dep for hmr\n            const importee = cleanUrl(\n              slash(path.resolve('/', srcAttr[1] || srcAttr[2]))\n            )\n            debugHmr(`        ${importer} imports ${importee}`)\n            ensureMapEntry(importerMap, importee).add(importer)\n          }\n          return matched\n        }\n      })\n    )\n  }\n\n  app.use(async (ctx, next) => {\n    await next()\n\n    if (ctx.status === 304) {\n      return\n    }\n\n    const { path } = ctx\n\n    if (isHtml(path)) {\n      if (rewriteHtmlPluginCache.has(path)) {\n        debug(`${path}: serving from cache`)\n        ctx.body = rewriteHtmlPluginCache.get(path)\n      } else {\n        const html = await readBody(ctx.body)\n        if (!html) return\n        ctx.body = await rewriteHtml(path, html)\n        rewriteHtmlPluginCache.set(path, ctx.body)\n      }\n      return\n    }\n  })\n\n  watcher.on('change', (file) => {\n    const path = resolver.fileToRequest(file)\n    if (isHtml(path)) {\n      rewriteHtmlPluginCache.del(path)\n      debug(`${path}: cache busted`)\n      watcher.send({\n        type: 'full-reload',\n        path,\n        timestamp: Date.now()\n      })\n      console.log(chalk.green(`[vite] `) + ` ${path} page reloaded.`)\n    }\n  })\n}\n\nfunction isHtml(path: string): boolean {\n  return path.endsWith('.html')\n}\n```\n\n\n\n# 492 - ba614ef history API fallback调整\n\n改动部分：\n\n- `node/server/serverPluginHtml.ts`: `hmr`脚本被调整为`import '${hmrClientPublicPath}'`语句引入\n- `node/server/serverPluginServeStatic.ts`：koa插件优先处理静态资源，放在`history API fallback`前方；`history API fallback`在检测到非`404`的状态，将不再进行处理。（详 **改动二**）\n\n> history API fallback是尽量为了我们打开serve提示的页面，能顺利跳转到`.html`\n\n### 改动二\n\n当我们import一个不存在的文件，会导致触发`history API fallback`，跳转到`index.html`，发送`.html`文件内容。\n\n```typescript\n# App.vue\nimport '/asdasdasda'\n```\n\n![1](1.png)\n\n![3](3.png)\n\n![2](2.png)\n\n`history api fallback`：检测到不是接受`test/html`将放弃跳转。\n\n![4](4.png)\n\n> 静态资源的提前检测，可以防止：假如我需要`http://localhost/a/b/c.html`，`history api fallback`检测到页面想要`index.html`，自动发送`index.html`资源，即用户等于访问`http://localhost/index.html`文件\n>\n> 所以静态资源的优先检测，`history api fallback`在知道不是404的情况下不会做跳转处理\n\n\n\n# 493 - 25852fa [#206](https://github.com/vitejs/vite/pull/206) 允许用户设置`knowNameExports` & 修复依赖优化缓存的处理目录\n\n改动部分：\n\n`node/depOptimizer.ts`：新增`resolveOptimizedCacheDir`方法，用于缓存**依赖优化所在的目录路径**，意义在于`node/resolver.ts`对于每次的`resolveBareModule`都会去读取一下**依赖优化所在的目录路径**，缓存起来可以有效减少文件信息读取的时间。\n\n> 这个 `knowNameExports` PR不重要，后续`@rollup/plugin-commonjs`会升级的。\n\n\n\n# 494 - 3752874 fix 当有内容才改写`html`\n\n`serverPluginHtml.ts`：检测到相应类型为`html`会进行`rewriteHtml`改写，现在需要检测上下文是否有内容，才改写，也是符合原本逻辑。\n\n\n\n# 495 - 74f8f0b [#209](https://github.com/vitejs/vite/pull/209) chore 单词修改\n\n`reoslved` -> `resolve`\n\n\n\n# 496 - 51610af 去除未使用的参数\n\n去除`node/server/serverPluginModuleRewrite.ts`中未使用的参数`config`。\n\n\n\n# 497 - dcfc994 changelog\n\n## [0.16.2](https://github.com/vuejs/vite/compare/v0.16.1...v0.16.2) (2020-05-20)\n\n### Bug Fixes\n\n- 调整history fallback ([ba614ef](https://github.com/vuejs/vite/commit/ba614ef59b576c2ea34baa580adb59d6d16625e8)), closes [#193](https://github.com/vuejs/vite/issues/193)\n- 改写`html`文件，需要判断上下文是否有内容(body) ([3752874](https://github.com/vuejs/vite/commit/3752874481ceef6188d5783d21e1fbc5e150a932))\n\n### Features\n\n- 允许用户设置 known named exports ([#206](https://github.com/vuejs/vite/issues/206)) ([25852fa](https://github.com/vuejs/vite/commit/25852fa8f7087ed50764a4a955a9397b930c1f87))\n\n\n\n# 498 - f74669a `vite v0.16.2`\n\nrelease`vite v0.16.2`\n\n\n\n# 499 - 0f41b06 readme\n\n`react`和`preact`都有开箱即用的`hmr`。\n\n但是基于`jsx`的`vue hmr`目前还不支持。\n\n> `vue`里用不了`jsx`，需要手动添加插件支持或者使用`jsx`文件。\n>\n> 看不懂这个readme了，反正目前react preact的hmr都是页面重新加载（SFC的为vue-reload）。\n\n\n\n# 500 - eedc985 fix [#210](https://github.com/vitejs/vite/issues/210) 修复依赖优化之间的交叉导入并避免重复\n\n改动部分：\n\n- `node/depOptimizer.ts`：添加`entryFileNames`，将输出和包一样的脚本名称，没有`.js`拓展名。\n- `node/resolver.ts`：`resolveOptimizedModule`中，去除`if (!id.endsWith('.js')) id += '.js'`，[传送门（曾经觉得这将会是一个BUG）](https://github.com/Kingbultsea/vite-analysis/blob/da8c66f4dd50db3014bb9d01d6e5210fa0848646/401-410/401-410.md#resolveoptimizedmodule)\n- `node/utils/fsUtils.ts`：`cachedRead`如果路径没有后缀名称，则默认`.js`作为`ctx.type`，即上下文类型\n\n**留坑**\n\n> 我没有感受到这个#210在描述一些什么... 不管怎么样都不会重复吧，比如你依赖A，`peerDev`了依赖B，依赖优化A不包含B代码（B为`cjs`）。如果依赖B是像`lodash-es`这样的，那么依赖A会包含依赖B的代码，同时依赖B也会在依赖优化中。\n\n"
  },
  {
    "path": "501-510/501-510.md",
    "content": "# 501 - ada8886 依赖优化的包，使用depImport且没有后缀，将输出warning\n\n改动部分：\n\n- `node/resolver.ts`：新增检测后缀 & jsSrcRE（详 **改动一**）\n\n> depImport即引入模块中某个指定的文件\n\n### 改动一\n\n```typescript\nexport const jsSrcRE = /\\.(?:(?:j|t)sx?|vue)$|\\.mjs$/ // 新增.mjs\nconst deepImportRE = /^([^@][^/]*)\\/|^(@[^/]+\\/[^/]+)\\//\n\nexport function resolveBareModule(root: string, id: string, importer: string) {\n  const optimized = resolveOptimizedModule(root, id)\n  if (optimized) {\n    return id\n  }\n  const pkgInfo = resolveNodeModuleEntry(root, id)\n  if (pkgInfo) {\n    return pkgInfo[0]\n  }\n\n  // check and warn deep imports on optimized modules\n  const ext = path.extname(id)\n  if (!ext || jsSrcRE.test(ext)) { // 新增检测 ~~~~~~~~~~~\n    const deepMatch = id.match(deepImportRE)\n    if (deepMatch) {\n      const depId = deepMatch[1] || deepMatch[2]\n      if (resolveOptimizedModule(root, depId)) {\n        console.error(\n          chalk.yellow(\n            `\\n[vite] Avoid deep import \"${id}\" since \"${depId}\" is a ` +\n              `pre-optimized dependency.\\n` +\n              `Prefer importing from the module directly.\\n` +\n              `Importer: ${importer}\\n`\n          )\n        )\n      }\n    }\n  }\n  return id\n}\n```\n\n> 基于上一个包的提醒，才更改的，因为上一个210介绍的就是组织包。@alicloud/XXX\n\n\n\n# 502 - 91f829d 为`protocol-less`的`url`修正external url的正则检测\n\n改动部分：\n\n- `node/utils/pathUtils.ts`：`httpRE`更名为`externalRE`（详 **改动一**）\n\n### 改动一\n\n```typescript\nconst externalRE = /^(https?:)?\\/\\// // http https 可有可无\nexport const isExternalUrl = (url: string) => externalRE.test(url)\n```\n\n> 什么是`protocol-less url`? `<img src=\"//domain.com/img/logo.png\">`\n\n\n\n# 503 - 7ab703c changlog\n\n## [0.16.3](https://github.com/vuejs/vite/compare/v0.16.1...v0.16.3) (2020-05-20)\n\n### Bug Fixes\n\n- 修正history fallback ([ba614ef](https://github.com/vuejs/vite/commit/ba614ef59b576c2ea34baa580adb59d6d16625e8)), closes [#193](https://github.com/vuejs/vite/issues/193)\n- 修正依赖优化打包出来的文件名称，去除后缀（原文意思不是这样，因为我不理解） ([eedc985](https://github.com/vuejs/vite/commit/eedc985b1f7108373a762b9d1fc94842fd40c17f)), closes [#210](https://github.com/vuejs/vite/issues/210)\n- 为`protocol-less`的`url`修正external url的正则检测 ([91f829d](https://github.com/vuejs/vite/commit/91f829dc1bfb5c1ed8411751b31f17c2322ed0a7))\n- html rewrite cache 应在内容存在的时候才执行([3752874](https://github.com/vuejs/vite/commit/3752874481ceef6188d5783d21e1fbc5e150a932))\n- 新增检测后缀 & jsSrcRE ([ada8886](https://github.com/vuejs/vite/commit/ada8886e36578c7f43b7cd12bacd65e5a7c4c6e4))\n\n### Features\n\n- 允许用户修改known named exports ([#206](https://github.com/vuejs/vite/issues/206)) ([25852fa](https://github.com/vuejs/vite/commit/25852fa8f7087ed50764a4a955a9397b930c1f87))\n\n\n\n# 504 - 65629b6 vite v0.16.3\n\nrelease vite v0.16.3\n\n\n\n# 505 - 161fe64 chore 修正单词名称\n\nexisiting -> existing\n\nviteHtmlTrasnfrom -> viteHtmlTransfrom\n\nbecuase -> because\n\nspeical -> special\n\napprorpriate -> appropriate\n\n\n\n# 506 - 09879b3 cssCodeSplit\n\n改动部分：\n\n- `build/index.ts`：chunkFileNames去除`common`更改为`[name]`，[传送门，应更改为名称](https://github.com/Kingbultsea/vite-analysis/blob/26210b660e6aeeb2c1be4a18216ff525f360e0aa/451-460/451-460.md#453---0cc57c8-%E4%B8%BAcss--%E4%B8%BBchunk%E6%B7%BB%E5%8A%A0hash)\n- `node/config.ts`：新增cssCodeSplit配置项，true则分割异步引入的css（目前没有看到相关使用，尤大漏了设置了，现在是默认打开这功能）\n- `node/build/buildPluginCss.ts`：rollup插件新增`renderChunk`（详 **改动三**）\n\n### 改动三\n\n```typescript\nasync renderChunk(code, chunk) {\n  // for each dynamic entry chunk, collect its css and inline it as JS\n  // strings.\n  if (chunk.isDynamicEntry) { // 检测到为异步chunk\n    let chunkCSS = ''\n    for (const id in chunk.modules) { // chunk引入了哪些模块\n      if (styles.has(id)) { // 询问是否有该模块id的相关styles代码\n        chunkCSS += styles.get(id) // 取出\n        styles.delete(id) // 去除\n      }\n    }\n    chunkCSS = await minifyCSS(chunkCSS) // 压缩\n    let isFirst = true // 可能cssInjectionMarker注入了两次\n    code = code.replace(cssInjectionRE, () => {\n      if (isFirst) {\n        isFirst = false\n        // make sure the code is in one line so that source map is preserved.\n        return (\n          `let ${cssInjectionMarker} = document.createElement('style');` +\n          `${cssInjectionMarker}.innerHTML = ${JSON.stringify(chunkCSS)};` +\n          `document.head.appendChild(${cssInjectionMarker});`\n        )\n      } else {\n        return ''\n      }\n    })\n  } else {\n    code = code.replace(cssInjectionRE, '') // 删除防止被tree-shaking掉的代码\n  }\n  return {\n    code,\n    map: null\n  }\n}\n```\n\n#### 大概流程是怎么样的？\n\n`transform`检测到`.css`，不是module的情况下注入`cssInjectionMarker`防止tree-shaking，并通过`styles`收集起来，key为id，`value`为`css`。\n\n`renderChunk`检测模块是不是异步引入（`isDynamicEntry`），是则检测模块引入了哪些子模块（`modules`），遍历这些子模块，寻找是否存在于`styles`，是则替换`cssInjectionMarker`为`js`创建`<style/>`标签的脚本。\n\n> 不用担心onMounted拿不到css，之前出现过link的问题，通过js脚本创建元素则不会\n\n#### css通过js脚本document.createElement引入，如何防止tree-shaking?\n\n\n在`transform`阶段 写一个副作用代码，如`a()`; 在renderChunk方法 删除`a()`。\n\n> cssInjectionMarker是为了防止css被tree-shaking掉，即添加`__VITE_CSS__()`，后续`renderChunk`将删除。\n\n> tips：如果不分割只会有一个style文件，所有css都会被transform钩子收集起来，generateBundle会生成一个style文件。\n\n> tips: SFC的css将交给rollup-plugin-vue自己加载生成，自己处理scoped，其他插件可以transform收集起来。\n\n\n\n# 507 - 8b95954 dev支持https [#208](https://github.com/vitejs/vite/pull/208)\n\n自定义证书，或者默认使用[`selfsigned`](https://www.baidu.com/link?url=dgCrVckTEV0tvDpbzmPo2NiQdx0xlgZhPvL8ZJAATXaOUYzKnJsAV4wQSW5nMSJh41X8hBXTXwIAlwltVyf7Wq&wd=&eqid=dabd30bf0003b9d40000000661704188)制造证书。\n\n> 也是参考了webpack的\n\n\n\n# 508 - b64ccee changelog\n\n## [0.16.4](https://github.com/vuejs/vite/compare/v0.16.3...v0.16.4) (2020-05-20)\n\n### Features\n\n- **dev:** 支持https ([#208](https://github.com/vuejs/vite/issues/208)) ([8b95954](https://github.com/vuejs/vite/commit/8b95954c87a04fae90be0a3e769f385a87182c8e))\n- 支持分割异步引入模块下的`styles` ([09879b3](https://github.com/vuejs/vite/commit/09879b30f321ca70789fd8afc3cd95bf68947698)), closes [#190](https://github.com/vuejs/vite/issues/190)\n\n\n\n# 509 - 1d80399 vite v0.16.4\n\nrelease vite v0.16.4\n\n\n\n# 510 - a1f5488 [#218](https://github.com/vuejs/vite/issues/218) 依赖优化下，被标记为外部引入的包需要调用resolver.alias转换，修复开发环境引入的问题。\n\n```typescript\nconst preservedDeps = deps\n.filter((id) => !qualifiedDeps.includes(id))\n// make sure aliased deps are external\n// https://github.com/vitejs/vite-plugin-react/issues/4\n.map((id) => resolver.alias(id) || id)\n```\n\n有些包需要指向正确的入口，所以通过alias设置，比如开发模式下：\n'@pika/react' -> '@pika/react-dom/source.development.js'\n\n构建模式引入'@pika/react'即可。\n\n"
  },
  {
    "path": "51-60/commit-51-60.md",
    "content": "# 51 - 86147d1 重构server路径\n\n```server```端，名称统一改成```node```端，即更改路径/文件夹名称\n\n新增了两个文件:\n\n- ```src/node/build.ts```：后续分析\n\n- ```src/node/index.ts```: `export * from './server'`\n\n\n\n# 52 - 0ea7970 ```rollup```与```postcss```，添加`vite build`打包功能\n\n## ```package.json```\n\n### ```postcss```\n\n一个转换使用```js```来转换```css```的工具\n\n### ```cssnano```\n\n```postcss```插件，优化体积\n\n### rollup\n\n打包管理\n\n## ```bin/vite.js```\n\n错误处理，当端口被占用，抛出error，重新使用新的端口运行服务\n\n## 新增```node/build.ts```\n\n使用```rollup```进行打包。引入```rollup```的方式很特别，有效减少打包vite这个工具包的体积（如文字说，```import```的作用仅仅是当作```type```来使用，会被```treeShaking```掉，可以构建后查看```build.js```，```rollup```的引入会被去除）\n\n``````typescri\nfunction build() {\n  // ...\n  // lazy require rollup so that we don't load it when only using the dev server\n  // importing it just for the types\n  const rollup = require('rollup').rollup as typeof Rollup\n}\n``````\n\n### 关于rollup\n\n有兴趣的可以了解一下[代码分割](https://rollup.docschina.org/guide/zh/#%E4%BB%A3%E7%A0%81%E5%88%86%E5%89%B2)\n\n#### vitePlugin (rollup的插件)\n\n> resolveID这种就是解析文件地址的意思，我们可以返回我们想返回的文件id(也就是地址，相对路径、决定路径)来让rollup加载。\n>\n> resolveID属于hookFirst钩子类型，如果返回null或者undefinded，下一个plugins的resolveId将不会被运行。也就是说谁按照顺序，处理冲突。\n\n```typescript\nconst vitePlugin: Plugin = {\n    name: 'vite', // warning与error会被标识为该名称\n    resolveId(id: string) { // 转换id名称，如果返回Null，则不做任何处理。\n      if (id.startsWith('/')) {\n        if (id === hmrClientPublicPath) {\n          return hmrClientPublicPath\n        } else {\n          return id.startsWith(root) ? id : path.resolve(root, id.slice(1))\n        }\n      } else if (id === 'vue') {\n        if (inlineVue) {\n          return resolveVue(root, true).vue\n        } else {\n          return {\n            id: cdnLink,\n            external: true\n              // 设置为true，将会被设置为相对路径\n              // https://github.com/rollup/rollup/issues/3940\n              // 比如转换为： import\"https://unpkg.com/vue@3.0.0-beta.4/dist/vue.esm-browser.prod.js\";\n          }\n        }\n      }\n    },\n    // https://www.rollupjs.com/guide/plugin-development#load\n    load(id: string) { // 返回sourcecode，同上，如果为Null，则不做处理\n      if (id === hmrClientPublicPath) {\n        return `export function hot() {}` // source code\n      } else if (id === indexPath) {\n        let script = ''\n        let match\n        \n        // 收集index.html外部的js\n        while ((match = scriptRE.exec(indexContent))) {\n          // TODO handle <script type=\"module\" src=\"...\"/>\n          // just add it as an import\n          script += match[1]\n        }\n        return script\n      }\n    }\n  }\n```\n\n目前没看出什么东西，对应的文件处理是```hmr```(本质就是```client/client.ts```)与```index.html```\n\n### cssExtractPlugin\n\n收集所有```styles code```，粘连所有```styles code```，生成一个```css```文件。\n\n需要放在处理SFC的Plugins的后面。\n\n```typescript\nconst cssExtractPlugin: Plugin = {\n    name: 'vite-css',\n    transform(code: string, id: string) {\n      if (id.endsWith('.css')) {\n        styles.set(id, code)\n        return '/* css extracted by vite */'\n      }\n    }\n  }\n```\n\n### rollup-plugin-vue\n\n处理SFC（在```dev```环境，我们可以借助```@vue/compiler-sfc```，来处理）。\n\n有兴趣的可以看这里：\n\nhttps://github.com/vuejs/rollup-plugin-vue/blob/next/src/index.ts\n\n和我们```dev```同一个作用。\n\n### @rollup/plugin-node-resolve\n\n处理Node的第三方包。\n\n### @rollup/plugin-replace\n\n打包的时候，修改字符串。\n\n```typescript\nrequire('@rollup/plugin-replace')({\n        'process.env.NODE_ENV': '\"production\"'\n})\n```\n\n### rollup-plugin-terser\n\n压缩代码。\n\n```typescript\n// 类型\ntype Last<T extends any[]> = [never, ...T][T['length']]\n```\n\n### rollup配置options\n\n```typescript\n{\n    // TODO\n    // parse index.html\n    // find entry file or script\n    // if inline script, create a temp main file next to it before bundling\n    input: path.resolve(root, 'index.html'), // rollup也可以处理html文件\n    plugins: [\n      vitePlugin,\n      require('rollup-plugin-vue')(),\n      require('@rollup/plugin-node-resolve')({\n        rootDir: root\n      }),\n      require('@rollup/plugin-replace')({\n        'process.env.NODE_ENV': '\"production\"'\n      }),\n      cssExtractPlugin,\n      require('rollup-plugin-terser').terser()\n    ]\n  }\n```\n\n```typescript\nconst { output } = await bundle.generate({\n    dir: outDir,\n    format: 'es'\n})\n```\n\n### 过程\n\n```vitePlugin```中的```load```方法并未处理```src```资源，仅处理了标签内的内容\n```<script src=\"main.js\">console.log(1)</script>```\n\n**但是我们可以把```console.log(1)```变成```import 'main.js'```，这样就可以完整打包vue程序了。**\n\n```typescript\n// vitePlugins中的load方法\n\nlet script = ''\net match\nconsole.log(indexContent)\nwhile ((match = scriptRE.exec(indexContent))) {\n  // TODO handle <script type=\"module\" src=\"...\"/>\n  // just add it as an import\n  script += match[1]\n}\nreturn script\n```\n\n- 配置好```rollup```，删除旧的文件夹后，创建出新的空的文件夹(生成代码的位置)，这个步骤是，vite需要再对输出的文件改写。\n\n- 植入```css```文件到```html文件模板```。\n\n\n```typescript\n`<link rel=\"stylesheet\" href=\"/${filename}\">`\n// 注意是link\n```\n\n- ```rollup```打包出来的```css```，全部字符串堆起来，交给```postcss```与```postcss```的插件处理，并生成css文件（等于改写）。\n\n- 检测有没有本地vue包，没有则cdn ```https://unpkg.com/vue@${vueVersion}/dist/vue.esm-browser.prod.js```，以```<script src=\"\" >```的形式\n\n- rollup打包出来的js，不改写，但是在```html```模板植入```<script/>```标签。\n\n- 最后，根据```html模板```（注入```<link/> <script/>```），生成```html```文件。\n\n## node/resolveVue.ts\n\n根据打包环境，如果运行在浏览器，则使用```esm-browser```包，如果在node环境，则使用```esm-bundler```，**后者不带编译器**，减少体积。\n\n\n\n# 53 - 7444c3e `--root src`\n\n修改测试的命令，路径为`src`。\n\n## 再细谈文件改动\n\n```typescript\nwatcher.on('change', async (file) => {\n    const timestamp = Date.now()\n    const servedPath = '/' + path.relative(root, file)\n    if (file.endsWith('.vue')) {\n        // 处理vue文件\n      handleVueSFCReload(file, servedPath, timestamp)\n    } else {\n        // 处理js文件\n      handleJSReload(servedPath, timestamp)\n    }\n  })\n```\n\n### ```handleVueSFCReload```\n\n1. 根据文件路径，询问```vueCache```这个```Set```是否有缓存，有则返回缓存```vue```文件（该文件正是```SFC组件```被```vuePlugins```处理好的```js```文件，如果不懂，可以理解为使用```vue-loader```转换```A.vue```组件为```js版本```的组件）。这里取缓存的作用是来分析```SFC```哪块标签变动，准确发送事件，性能优化。\n\n2. 因为文件已经改动了，所以删除缓存结果```vueCache.del(file)```\n\n3. ```parseSFC```，读取```vueCache```缓存(没有缓存了，被上一步删除了，在**文件改动中**无效，已验证)，```SFC```转换成```js```组件处理\n\n4. ```parseSFC```对于一些有BUG的```SFC组件```，无法处理，那么就不会返回```descriptor```这个字段，就是说流程结束。\n\n5. 如果```SFC组件```被成功转换成```js组件```：\n   ```<script>```变动：发送```vue-reload事件```（**更新字段，通知父组件重新渲染vnode**，```instance.update```，如忘了这些事件，请查看**commit-6**的```reload事件```）\n\n   ```typescript\n   import { updateStyle } from \"/@hmr\"\n   \n   import { render as __render } from \"/Child.vue?type=template&t=1627148468989&t=1627148468989\"\n   __script.render = __render\n   __script.__scopeId = \"data-v-92a6df80\"\n   __script.__hmrId = \"/Child.vue\"\n   __script.__file = \"E:\\\\vite\\\\test\\\\fixtures\\\\Child.vue\"\n   export default __script\n   \n   // 更新的字段就是render __hmrId __file\n   ```\n\n   ```<template>```变动：发送```vue-rerender```事件（**重新渲染自身组件vnode**，```instance.update```，[commit-6-rerender](https://github.com/Kingbultsea/vite-analysis/blob/a1a7c85a55909ac4457dc2fe40f3eebdccec2ad1/readme.md#%E5%85%B3%E4%BA%8Ererender%E4%BA%8B%E4%BB%B6)）\n\n   ```<style>```变动：\n     如果标签内的属性```scoped```变动，发送```vue-reload```事件，去除```js组件```的```__scopeId```;\n     如果内容改变，则发送```vue-style-update```事件；\n\n     ```typescript\n     function updateStyle(id: string, url: string) {\n       const linkId = `vite-css-${id}`\n       let link = document.getElementById(linkId)\n       if (!link) {\n         link = document.createElement('link')\n         link.id = linkId\n         link.setAttribute('rel', 'stylesheet')\n         link.setAttribute('type', 'text/css')\n         document.head.appendChild(link)\n       }\n       link.setAttribute('href', url)\n     }\n     ```\n\n   如果旧的```<styles/>```标签数量多于新的，则发送```vue-style-remove```事件；\n\n   ```typescript\n   case 'vue-style-remove':\n         const link = document.getElementById(`vite-css-${id}`)\n         if (link) {\n           document.head.removeChild(link)\n         }\n         break\n   ```\n\n### ```handleJSReload```\n\n1. 分析```importerMap```，取出所有```import```了**当前文件的文件路径**。\n2. 如果引入方为```vue```文件，触发```vue-reload```\n3. 如果非`vue`文件（就当是`js`文件），分析是谁引入该`js`文件（称它为`importer`），如果是`vue`组件引入了，则保存在一个数组中，后续遍历触发`vue-reload`事件。如果是`js`文件引入了，且分析`isHMRBoundary`是否有该文件，如果有，则保存在一个数组中，后续遍历触发`js-update`。以上都没有，则查看`importer`是否有`父importer`，有则重复调用一次该过程`importee`更改为`父importer`，没有则触发`full-reload`。\n\n#### 什么是isHMRBoundary?\n\n```typescript\n# fc.js源文件\nimport { foo } from './foo.js'\nimport { hot } from '@hmr'\n\nfoo()\n\nhot.accept('./foo.js', ({ foo }) => { // callBack\n  // the callback receives the updated './foo.js' module\n  foo()\n})\n```\n\n`foo.js`，可以分析出`importer`为`fc.js`，`isHMRBoundary`会查找fc.js为键的值（一个Set）是否包含`foo.js`，包含则触发`callback`，不包含则（根据上面文件`import`语句）重复`3`的过程，`importee`从`foo.js`改为`fc.js`。\n\nwindows中存在BUG，需要修改:\n\n```typescript\nwatcher.on('change', async (file) => {\n    const timestamp = Date.now()\n    console.log(root, file)\n    const servedPath = '/' + path.relative(root, file)\n    if (file.endsWith('.vue')) {\n      handleVueSFCReload(file, servedPath, timestamp)\n    } else {\n        // savedPath 需要替换 '/' 为 '\\'\n      handleJSReload(servedPath, timestamp)\n    }\n  })\n```\n\n\n\n# 54 - e8f9175 chore\n\n更新rollup-plugin-vue包\n\n```json\n{\n-    \"rollup-pkugin-vue\": \"6.0.0-alpha.1\"\n+    \"rollup-pkugin-vue\": \"6.0.0-alpha.1\"\n}\n```\n\n\n\n# 55 - fbbdb19 `vue`组件支持`css module`\n\n```vue\n<style module>\n    .foo {\n        color: red;\n    }\n</style>\n```\n\n```typescript\nexport declare interface SFCStyleBlock extends SFCBlock {\n    type: 'style';\n    scoped?: boolean;\n    module?: string | boolean;\n}\n```\n\n```css-module```原理是生成一个js文件:\n\n```typescript\nexport default {\"foo\":\"_foo_dq1k9_3\"}\n```\n\n使用的时候访问```$style.foo```，即返回```_foo_dq1k9_3```\n\n```js\nimport { updateStyle } from \"/@hmr\"\n\nimport Child from './Child.vue'\nimport tool from './tool.js'\n\nconst __script = {\n  components: { Child },\n  setup() {\n    return {\n      count: 2\n    }\n  }\n}\n\nconst __cssModules = __script.__cssModules = {}\nimport __style0 from \"/Comp.vue?type=style&index=0&module\"\n__cssModules[\"$style\"] = __style0\nupdateStyle(\"92a6df80-0\", \"/Comp.vue?type=style&index=0\")\nimport { render as __render } from \"/Comp.vue?type=template\"\n__script.render = __render\n__script.__hmrId = \"/Comp.vue\"\n__script.__file = \"E:\\\\vite\\\\test\\\\fixtures\\\\Comp.vue\"\nexport default __script\n```\n\n## `vue css module`知识\n\n```typescript\nconst publicPropertiesMap: Record<\n  string,\n  (i: ComponentInternalInstance) => any\n> = {\n  $: i => i,\n  $el: i => i.vnode.el,\n  $data: i => i.data,\n  $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),\n  $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),\n  $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),\n  $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),\n  $parent: i => i.parent && i.parent.proxy,\n  $root: i => i.root && i.root.proxy,\n  $emit: i => i.emit,\n  $options: i => (__FEATURE_OPTIONS__ ? resolveMergedOptions(i) : i.type),\n  $forceUpdate: i => () => queueJob(i.update),\n  $nextTick: () => nextTick,\n  $watch: __FEATURE_OPTIONS__ ? i => instanceWatch.bind(i) : NOOP\n}\n```\n\n当我们访问属性```$style```的时候，即是访问```instance.proxy.$style```(vue-next的知识，和普通熟悉访问```data```是一样的，只不过做了一个特殊处理```$```开头命名的变量)，我们询问```publicPropertiesMap```中有没有```$style```这个东西，发现没有，则代表访问```type.__cssModules```:\n\n``````typescript\n// # vue-next源码 instance.proxy handle部分\n\nconst publicGetter = publicPropertiesMap[key]\n    let cssModule, globalProperties\n    // public $xxx properties\n    if (publicGetter) {\n      if (key === '$attrs') {\n        track(instance, TrackOpTypes.GET, key)\n        __DEV__ && markAttrsAccessed()\n      }\n      return publicGetter(instance)\n    } else if (\n      // css module (injected by vue-loader)\n      (cssModule = type.__cssModules) &&\n      (cssModule = cssModule[key])\n    )\n``````\n\n![type字段详解](./global.png)\n\n## 关于这一块的HMR\n\n处理SFC组件的时候，添加了\n\n```typescript\nconst __cssModules = __script.__cssModules = {}\nimport __style0 from \"/Comp.vue?type=style&index=0&module\"\n__cssModules[\"$style\"] = __style0\nupdateStyle(\"92a6df80-0\", \"/Comp.vue?type=style&index=0\")\n```\n\n如果我们在```<style>```标签上添加，或者删除属性，```client```端都会触发```vue-style-update```事件，这表示如果**删除**，并不会引起任何```js```上的改变，也就是说```__cssModules[\"$style\"] = __style0```依旧存在，这与```scopedId```是同理的。\n\n注：新增会触发```vue-reload```事件（```!isEqual(descriptor.script, prevDescriptor.script)```引起的）\n\n\n\n# 55 - b6d1d06 错误提示 （不小心写到55了，实际是56）\n\n在各种错误捕获中，增加提示。\n\n\n\n# 56 - 5a7a4e2 在plugin-vue中使用css预处理器\n\n```typescript\nplugins:[\n    // ...\n    require('rollup-plugin-vue')({\n        // TODO: for now we directly handle pre-processors in rollup-plugin-vue\n        // so that we don't need to install dedicated rollup plugins.\n        // In the future we probably want to still use rollup plugins so that\n        // preprocessors are also supported by importing from js files.\n        preprocessStyles: true,\n        preprocessCustomRequire: (id: string) => require(resolve(root, id))\n        // TODO proxy cssModules config\n      })\n    // ...\n]\n```\n\n在处理```SFC组件```中使用预处理器，这样我们就不需要在```rollup```中使用```css预处理器```了\n\n[rollup-plugin-vue配置传送门](https://github.com/vuejs/rollup-plugin-vue#options)\n\n\n\n# 57 - 3ba7e8e 修改readme\n\n从版本^0.5.0开始，你可以运行vite build打包应用程序并将其部署到生产环境中。\n\n在内部，我们使用一个默认配置来生成构建。目前有意没有公开方法来配置构建——但我们可能会在稍后阶段解决这个问题。\n\n\n\n# 58 - 300af62 发布v0.5.0\n\nrelease v0.5.0\n\n\n\n# 59 - 63e5e28 修改变量名称\n\n把```inlineVue```**变量名称**修改为```cdn```，```inlineVue```字段代表是否有本地```node_modules```里的```vue```包，没有则使用```cdnLink```。\n\n```typescript\nlet inlineVue = !resolveVue(root).hasLocalVue // inlineVue -> cdn\nif (inlineVue) { // inlineVue -> cdn\n  // 使用cdnLink\n}\n```\n\n根据代码上看，更符合语义。\n\n\n\n# 60 - b3d69b1 使`cdn`与`vite`环境下的`vue`版本保持一致\n\n使```cdn```上的```vue```版本与使用`vite`开发的```vue```版本一致。\n\n## 疑惑点：都已经有本地包了，不会使用```cdn```了，再引入version有何意义？\n\n**不是这样的**，```version```字段，在没有本地`Vue`包的时候，会使用```require('vue/package.json').version```，**这样就可以寻找到`vite`开发所依赖的`vue`包的版本号**。\n\n"
  },
  {
    "path": "511-520/511-520.md",
    "content": "# 511 - 8a9710b 如果`index.html`不存在，则设置`ctx.url`为`/index.html`\n\n- `node/server/serverPluginServerStatic.ts`：`history API fallback`中如果`index.html`不存在，则重定向`url`（详 **改动一**）\n\n### 改动一\n\n即`history API fallback`如果符合条件跳转，但是`index.html`不存在，则设置404，且重定向到`/index.html`。\n\n```typescript\ntry {\n    await send(ctx, `index.html`)\n} catch(e) {\n    ctx.url = '/index.html'\n    ctx.status = 404\n    return next()\n}\n```\n\n> 这并不叫重定向，只是`ctx`洋葱模型中知道ctx.url被设置了，但是浏览器回收到404，不是浏览器重定向，如果想要浏览器重定向，要使用` ctx.redirect('/index.html')`;`ctx.status = 302`\n\ntips: 301: 旧地址A的资源不可访问了(永久移除), 重定向到网址B，搜索引擎会抓取网址B的内容，同时将网址保存为B网址。 302: 旧地址A的资源仍可访问，这个重定向只是临时从旧地址A跳转到B地址，这时搜索引擎会抓取B网址内容，但是会将网址保存为A的。\n\n\n\n# 512 - 4a0b6cc 配置参数`cssCodeSplit`\n\n- 遗留`cssCodeSplit`在代码中参数传递，默认`true`。\n- `node/build/index.ts`：`ssrBuild`中设置`rollupInputOptions.entryFileNames = '[name].js'`\n\n\n\n# 513 - 0eb7c2f changelog\n\n## [0.16.5](https://github.com/vuejs/vite/compare/v0.16.4...v0.16.5) (2020-05-21)\n\n### Bug Fixes\n\n- index.html不存在则设置`ctx.url = '/index.html'` ([df733d9](https://github.com/vuejs/vite/commit/df733d9cd93ad1d1d01c11b8b7a3a9659a7b9cbf))\n- 完善`cssCodeSplit`选项([3751551](https://github.com/vuejs/vite/commit/375155164ec68c78f07fc57d34cdc477249dc3a2))\n\n\n\n# 514 - 83697cc v0.16.5\n\nrelease v0.16.5\n\n\n\n# 515 - 03b0ce5 changelog\n\n## [0.16.6](https://github.com/vuejs/vite/compare/v0.16.4...v0.16.6) (2020-05-21)\n\n### Bug Fixes\n\n- 依赖优化下，被标记为外部引入的包需要调用`resolver.alias`转换，修复开发环境引入所导致的问题([#218](https://github.com/vuejs/vite/issues/218)) ([a1f5488](https://github.com/vuejs/vite/commit/a1f54889a95a24f89804b0fbdfc876cde5615c98))\n- index.html不存在则设置`ctx.url = '/index.html'`([8a9710b](https://github.com/vuejs/vite/commit/8a9710b1a90cadfa69889cf00c224ea41ca13a9f))\n- 完善`cssCodeSplit`选项 ([4a0b6cc](https://github.com/vuejs/vite/commit/4a0b6cc573840f3f74ac4f1b59bc957f1c626a92))\n\n> 还是第一次见尤打发版本遗漏了，即0.16.5的描述是不完善的，所以重新发一次，而不是删除v0.16.5，我之前发版会进行一个删除，免得难看。\n\n\n\n# 516 - b0b0734 v0.16.6\n\nrelease v0.16.5\n\n\n\n# 517 - 457f1f2 `ssrBuild`的`cssCodeSplit`选项为`false`\n\n不深入研究SSR。\n\n\n\n# 518 - e741628 洋葱模型调整（更新-3）\n\n本次提高可以把`import`路径重写的功能提高最高优先级（最后执行，洋葱模型第一层的外层为最后执行）。\n\n`htmlPlugin`更名为`htmlRewritePlugin`，这个插件仅比`moduleRewritePlugin`第一级，它只处理改写`html`，资源在第13层中会配置，`await readBody(ctx.body)`可以获取到。\n\n\n\n![1](1.png)\n\n```typescript\nconst resolvedPlugins = [\n    moduleRewritePlugin,      // 洋葱模型的第一层  （外层） --》》》\n    htmlRewritePlugin,        // 洋葱模型的第二层  （外层）\n    ...(Array.isArray(configureServer) \n        ? configureServer \n        : [configureServer]), // 洋葱模型的第三层  （自定）\n    moduleResolvePlugin,      // 洋葱模型的第四层  （里层）\n    proxyPlugin,              // 洋葱模型的第五层  （里层）\n    serviceWorkerPlugin,      // 洋葱模型的第六层  （里层）\n    hmrPlugin,                // 洋葱模型的第七层  （里层）\n    vuePlugin,                // 洋葱模型的第八层  （里层）\n    cssPlugin,                // 洋葱模型的第九层  （里层）\n    ...(transforms.length \n        ? \n        [createServerTrans\n         formPlugin(transforms)\n        ] : []),              // 洋葱模型的第10层  （外层）--》》》\n    esbuildPlugin,            // 洋葱模型的第11层  （外层）--》》》\n    jsonPlugin,               // 洋葱模型的第12层  （里层）--》》》\n    assetPathPlugin,          // 洋葱模型的第13层  （里层）--》》》\n    serveStaticPlugin         // 洋葱模型的第14层  （里层）--》》》\n  ]\n\n// 旧的 第二版\nconst internalPlugins: Plugin[] = [\n  ...config.plugins,     // 洋葱模型的第一层  （自定）\n  hmrPlugin,             // 洋葱模型的第二层  （里层）  \n  moduleRewritePlugin,   // 洋葱模型的第三层  （外层） --\n  moduleResolvePlugin,   // 洋葱模型的第四层  （里层）\n  vuePlugin,             // 洋葱模型的第五层  （里层）\n  esbuildPlugin,         // 洋葱模型的第六层  （外层） --\n  jsonPlugin,            // 洋葱模型的第七层  （外层） --\n  cssPlugin,             // 洋葱模型的第八层  （外层） --\n  assetPathPlugin        // 洋葱模型的第九层  （里层） \n  ServerTransformPlugin, // 洋葱模型的第十层  （外层） -- \n  serveStaticPlugin      // 洋葱模型的第十一层（里层）\n]\n```\n\n> 根据重要性决定是里层还是外层，像一些资源获取，都是里层，放到最后，可以让资源到前面几层中没有击中目标后再处理\n\n\n\n# 519 - 4637556 chore，调用脚本直接使用名称\n\n`node`是支持默认寻找`index.js`的。\n\n![2](2.png)\n\n\n\n# 520 - 029de6b 不要改写Fragment类型的url资源\n\n`node/utils/cssUtils.ts`：`url(#XXXX)`，不需要改写。\n\n> `css`里有一个`filter: url(#svg)`，表示svg元素id。\n\n"
  },
  {
    "path": "521-530/521-530.md",
    "content": "# 521 - 89fe0a9 支持从`.env`文件加载`env`属性 [#223](https://github.com/vitejs/vite/issues/223)\n\n改动部分：\n\n- `package.json`：新增`dotenv`包，`Dotenv `是一个零依赖模块，它将环境变量从` .env `文件加载到 `process.env` 中。\n- `node/config.ts`：新增`env`，类型为`DotenvParseOutput`，可以直接通过`vite.config`配置，不需要`.env`文件；检测项目启动路径下的`.env`文件，有则直接调用`require('dotenv').config()`，同时覆盖`config.env`为`dotenv`配置后的对象字段。\n- `node/build/index.ts`：与`NODE_ENV: 'production'`合并`config.env`，替换`process,env`。\n- `node/server/serverPluginHtml.ts`：与`NODE_ENV: 'development'`合并`config.env`，替换`client`端的`window.process`。\n- 添加serve的测试。\n\n\n\n# 522 - 1be6121 修正build环境下的`env`替换\n\n改动部分：\n\n- `node/build/index.ts`：合并的代码更改（详 **改动一**）\n\n### 改动一\n\n```typescript\nenv.NODE_ENV = 'production'\nconst envReplacements = Object.keys(env).reduce((replacements, key) => {\n    replacements[`process.env.${key}`] = JSON.stringify(env[key])\n}, {} as Record<string, string>)\n\n// rollup vite:replace插件\n{\n    ...envReplacements, // 优先替换\n    'process.env.': `({}).`  // 默认空对象\n}\n```\n\n> 构建是字符串的替换，serve下是一整个对象，所以方式不一样\n\n\n\n# 523 - b96ed68 不应该更换依赖包内的字符串\n\n改动部分：\n\n- `node/build/index.ts`：在`createReplacement`中，判断id是不是带有`node_modules`，是则不进行替换。\n\n> 外部调用是传递`callBack`，所以能获取到`rollup`的`id`。\n\n\n\n# 524 - c493629 [#227](https://github.com/vitejs/vite/issues/227) 修复条件导出下无法引入package.json的BUG\n\n改动部分：\n\n- `node/resolver.ts`：方法名称更改`resolveNodeModuleEntry` -> `resolveNodeModule`，支持寻找不在`node_modules`或没有`package.json`的包（详 **改动一**）\n\n> [conditional exports](https://nodejs.org/api/packages.html#conditional-exports)：条件导出。在windows下没有遇到这问题，node把`package.json`当成路径了，比如我们需要设置为`{ \"package.json\": { \"default\": \"package.json\" }  }`才能取得`package.json`\n>\n> [react-native #1168](https://github.com/react-native-community/cli/issues/1168#issuecomment-628034263)\n>\n> ```\n> Uncaught:\n> Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './package.json' is not defined by \"exports\" in /Users/foobar/my-react-project/node_modules/utils-ts/package.json\n> ```\n\n### 改动一\n\n通过`require.resolve`寻找`pkg1/package.json`，假如寻找不了，则寻找\n`resuire.resolve('pkg1', { paths: [root] })`，\n取得完整路径后，拼接出`XXXX/.node_modules/pkg1/package.json`。\n\n```typescript\nE:\\vite\\playground\\node_modules\\pkg1\\index.js\nE:\\vite\\playground\\node_modules\\pkg1\\package.json\n```\n\n\n\n# 525 - daa2ddd 更新`circleci`状态，`url`\n\n更新`circleci`状态。\n\n![2](2.png)\n\n\n\n# 526 - bef67f5 [#220](https://github.com/vitejs/vite/pull/220) 使用`vue`的内置预处理器\n\n主要改动：\n\n- 去除引入`postcss`预处理器，统一通过`@vue/compiler-sfc`下的`compileStyleAsync`进行处理。\n- 用户定义的插件不再支持`css`的转换，所有`css`默认使用`postcss`。\n\n> 实际也是`postcss`，只不过省掉了`vite`下再次`requrie`，把代码封装起来。\n\n改动部分：\n\n- `node/transform.ts`：as字段去除`css`，即用户不能再对`css`进行转换\n- `node/build/buildPluginCss.ts`：默认转换`/(.+).(css|less|sass|scss|styl|stylus)$/`后缀`id`（详 **改动二**）\n- `node/server/serverPluginCss.ts`：监听`/(.+).(css|less|sass|scss|styl|stylus)$/`后缀的文件变动，同build模式的转换。（详 **改动三**）\n- `node/server/serverPluginHmr.ts`：去除`cssTransforms`，改由**非**`/(.+).(css|less|sass|scss|styl|stylus)$/`监听文件触发`hmr`。\n- `server/serverPluginVue.ts`：改由`compilerCss`来处理SFC下的`<style>`，监听style的进行`hmr`的逻辑被丢到`node/server/serverPluginCss.ts`。\n- `node/utils/cssUtils.ts`：新增`comiplerCss`方法，一个封装`compileStyleAsync`逻辑的`css`预处理器。（详 **改动六**）\n\n> `compileCss`遇到`.css` | `非.module.css` | `没有postcss.config配置`，不会进行任何转换，直接返回本身作为结果\n\n### 改动二\n\n主要是去除了`transfrom`，统一使用被封装的`compileCss`\n\n```typescript\nasync transform(css: string, id: string) {\n  if (id.endsWith('.css') || cssPreprocessLangRE.test(id)) {\n    const result = await compileCss(root, id, {\n      id: '',\n      source: css,\n      filename: path.basename(id),\n      scoped: false,\n      modules: id.endsWith('.module.css'),\n      preprocessLang: id.replace(cssPreprocessLangRE, '$2') as any\n    })\n\n    let modules: SFCStyleCompileResults['modules']\n    if (typeof result === 'string') {\n      css = result\n    } else {\n      if (result.errors.length) {\n        console.error(`[vite] error applying css transforms: `)\n        result.errors.forEach(console.error)\n      }\n      // css moduels\n      css = result.code\n      modules = result.modules\n    }\n\n    // process url() - register referenced files as assets\n    // and rewrite the url to the resolved public path\n    if (urlRE.test(css)) {\n      const fileDir = path.dirname(id)\n      css = await rewriteCssUrls(css, async (rawUrl) => {\n        const file = path.posix.isAbsolute(rawUrl)\n          ? path.join(root, rawUrl)\n          : path.join(fileDir, rawUrl)\n        const { fileName, content, url } = await resolveAsset(\n          file,\n          root,\n          publicBase,\n          assetsDir,\n          inlineLimit\n        )\n        if (fileName && content) {\n          assets.set(fileName, content)\n        }\n        debug(\n          `url(${rawUrl}) -> ${\n            url.startsWith('data:') ? `base64 inlined` : `url(${url})`\n          }`\n        )\n        return url\n      })\n    }\n\n    styles.set(id, css)\n    return {\n      code: modules\n        ? `export default ${JSON.stringify(modules)}`\n        : cssCodeSplit\n        ? // If code-splitting CSS, inject a fake marker to avoid the module\n          // from being tree-shaken. This preserves the .css file as a\n          // module in the chunk's metadata so that we can retrive them in\n          // renderChunk.\n          `${cssInjectionMarker}()\\n`\n        : ``,\n      map: null\n    }\n  }\n},\n```\n\n### 改动三\n\n```typescript\nasync function processCss(root: string, ctx: Context) {\n  let css = (await readBody(ctx.body))!\n\n  const result = await compileCss(root, ctx.path, {\n    id: '',\n    source: css,\n    filename: resolver.requestToFile(ctx.path),\n    scoped: false,\n    modules: ctx.path.endsWith('.module.css'),\n    preprocessLang: ctx.path.replace(cssPreprocessLangRE, '$2') as any\n  })\n\n  if (typeof result === 'string') {\n    processedCSS.set(ctx.path, { css })\n    return\n  }\n\n  if (result.errors.length) {\n    console.error(`[vite] error applying css transforms: `)\n    result.errors.forEach(console.error)\n  }\n\n  result.code = await rewriteCssUrls(result.code, ctx.path)\n\n  processedCSS.set(ctx.path, {\n    css: result.code,\n    modules: result.modules\n  })\n}\n```\n\n### 改动六\n\n```typescript\nexport const cssPreprocessLangRE = /(.+).(less|sass|scss|styl|stylus)$/\n\nexport async function compileCss(\n  root: string,\n  publicPath: string,\n  {\n    source,\n    filename,\n    scoped,\n    modules,\n    preprocessLang\n  }: SFCAsyncStyleCompileOptions\n): Promise<SFCStyleCompileResults | string> {\n  const id = hash_sum(publicPath)\n  const postcssConfig = await loadPostcssConfig(root)\n  const { compileStyleAsync } = resolveCompiler(root)\n\n  if (publicPath.endsWith('.css') && !modules && !postcssConfig) {\n    // 纯css \n    return source\n  }\n\n  return await compileStyleAsync({\n    source,\n    filename,\n    id: `data-v-${id}`,\n    scoped,\n    modules,\n    modulesOptions: {\n      generateScopedName: `[local]_${id}`\n    },\n    preprocessLang: preprocessLang,\n    preprocessCustomRequire: (id: string) => require(resolveFrom(root, id)),\n    ...(postcssConfig\n      ? {\n          postcssOptions: postcssConfig.options,\n          postcssPlugins: postcssConfig.plugins\n        }\n      : {})\n  })\n}\n```\n\n\n\n# 527 - 33c6bc2 chore更新`readme`中可以直接使用`.scss`\n\n开箱即用。![3](3.png)\n\n\n\n# 528 - 7a3e822 [#230](https://github.com/vitejs/vite/pull/230) `requestToFile`在包的入口被改写下，需要返回真正的路径（无效的PR，需要修复一下路径）\n\n在改写路径的插件中`serverPluginModuleRewrite`，会根据`importer`，与`import`路径的合并，得出文件所在位置，即`resolveRelativeRequest`方法。\n\n但是`resolveRelativeRequest`遇到没有后缀拓展名称的`importer`，会把最后一层路径给去除(即`path.dirname(importer)`)。\n\n比如现在有一个包为`pkg`，入口为`./index.js`：\n\n```typescript\n# main.js 我们引入pkg\nimport 'pkg'\n\npkg |\n    |- index.js\n    |- A\n       |- index.js\n       |- index2.js\n# pkg/index.js\nimport './A'\n\n# pkg/A/index.js\nimport './index2.js'\n```\n\n会被改写为：\n\n```typescript\n# main.js 我们引入pkg\nimport '/@modules/pkg/index.js'\n\npkg |\n    |- index.js\n    |- A\n       |- index.js\n       |- index2.js\n\n# pkg/index.js\n// importer为'/@modules/pkg/index.js'，\n// resolveRelativeRequest合并得 '/@modules/pkg/A'\n// 拓展名检测resolver.requestToFile，\n// 经defaultRequestToFile的path.join(root, publicPath.slice(1)) ‘你的项目/@modules/pkg/A'\n// 经resolveExt得 ‘你的项目/@modules/pkg/A'\n// ‘你的项目/@modules/pkg/A'没有拓展名称，不做任何处理\n// 所以结果为resolveRelativeRequest得出的'@modules/pkg/A'\n// _______后面会详细说________，defaultRequestToFile里本意是去除@modules,返回原本file路径\nimport '@modules/pkg/A'\n\n# pkg/A/index.js\n// importer为'@modules/pkg/A'\n// resolveRelativeRequest合并得'@modules/pkg/index2.js’\nimport '@modules/pkg/index2.js’\n```\n\n`'@modules/pkg/index2.js’`根本就不存在，所以寻找不了，程序崩溃。\n\n那我们是不是应该修复`resolveRelativeRequest`可以得出正确的import文件路径？\n\n`vite`在检测到`resolveRelativeRequest`返回的路径没有后缀拓展名称(**修复后不会没有后缀，所以不会出现该情况**)，会进行以下代码，尝试添加后缀拓展名称：\n\n```typescript\n// append an extension to extension-less imports\n    if (!path.extname(pathname)) {\n      const file = resolver.requestToFile(pathname)\n      const indexMatch = file.match(/\\/index\\.\\w+$/)\n      if (indexMatch) {\n        pathname = pathname.replace(/\\/(index)?$/, '') + indexMatch[0]\n      } else {\n        pathname += path.extname(file)\n      }\n    }\n```\n\n我们不能通过`resolveRelativeRequest`来修复(**修复后不会没有后缀，所以不会出现该情况**)，因为我们需要知道`import './A'`，想要引入的到底是哪个文件，就必须通过`resolver.requestToFile`来检测，在上述例子中，正确的检测为`A/index.js`。\n\n`resolver.requestToFile`里又调用了`defaultRequestToFile`：\n\n```typescript\nconst defaultRequestToFile = (publicPath: string, root: string): string => {\n  // 遇到开头为@modules直接去除，idToFileMap寻找原本file路径\n  if (moduleRE.test(publicPath)) {\n    const moduleFilePath = idToFileMap.get(publicPath.replace(moduleRE, ''))\n    if (moduleFilePath) {\n      return moduleFilePath\n    }\n  }\n    \n  // 去除第一位，如 /@modules/ABC 去除 /ABC 中的 / 即得出ABC\n  return path.join(root, publicPath.slice(1))\n}\n```\n\n把`resolveRelativeRequest`得出的路径，比如上述例子`'/@modules/pkg/A'`，改造成为`你的项目位置/pkg/A`，我们想做的动作是：\n\n寻找`'/@modules/pkg/A'`的真正入口是哪里，即\n\n`idToFileMap.get(publicPath.replace(moduleRE, ''))`，但是`'/@modules/pkg/A'`还没有被`serverPluginModuleResolve`处理，建立`id`与文件入口的关系。\n\n所以，我们在`idToFileMap`获取不到文件路径的情况下，去主动调用`serverPluginModuleResolve`的寻找包入口并建立id与文件路径的逻辑。\n\n```typescript\nconst defaultRequestToFile = (publicPath: string, root: string): string => {\n  if (moduleRE.test(publicPath)) {\n    const id = publicPath.replace(moduleRE, '')\n    const cachedModuleFilePath = idToFileMap.get(id)\n    if (cachedModuleFilePath) {\n      return cachedModuleFilePath\n    }\n    const resolved = resolveNodeModuleFile(root, id)\n    if (resolved) {\n      idToFileMap.set(id, resolved)\n      return resolved\n    }\n  }\n  return path.join(root, publicPath.slice(1))\n}\n```\n\n当前还需要修改：\n\n![5](5.png)\n\n```typescript\nif (moduleRE.test(pathname)) {\n        pathname = file.replace(root, '/@modules').replace(/\\\\/g, '/')\n      } else {\n        const indexMatch = file.match(/\\/index\\.\\w+$/)\n        if (indexMatch) {\n          pathname = pathname.replace(/\\/(index)?$/, '') + indexMatch[0]\n        } else {\n          pathname += path.extname(file)\n        }\n      }\n```\n\n即检测到是模块，则直接替换文件路径，而不是添加后缀。\n\n![6](6.png)\n\n成功！想要自己修复的可以去玩一下：https://github.com/csr632/test-vite/tree/wrong-resolve\n\n\n\n# 529 - 312faee 去除`css transform`类型\n\n`css`的`transfrom`已废弃，需要在类型中去除。\n\n\n\n# 530 - 5eea84d changelog\n\n- ## [0.16.7](https://github.com/vuejs/vite/compare/v0.16.6...v0.16.7) (2020-05-22)\n\n  ### Bug Fixes\n\n  - defaultRequestToFile 需要返回真正的包入口file路径 ([#230](https://github.com/vuejs/vite/issues/230)) ([7a3e822](https://github.com/vuejs/vite/commit/7a3e822597b94f8440e7436e3cc54a2764fff4eb)), closes [#228](https://github.com/vuejs/vite/issues/228)\n  - ssr build禁止cssCodeSplit  ([457f1f2](https://github.com/vuejs/vite/commit/457f1f2aca32f968f4ffe822633a2b4c49456fd4))\n  - css urls 是 hash fragments的情况下不需要改写 ([029de6b](https://github.com/vuejs/vite/commit/029de6b30bfc307d4b02f28703cd8d73a706b1cd))\n  - 修正build环境下的`env`替换 ([1be6121](https://github.com/vuejs/vite/commit/1be61219d1e253d6edec812ff7828b69d775c093))\n  - 保证rewrite middlewares拥有最高改写权 ([e741628](https://github.com/vuejs/vite/commit/e74162857ad33788f6fa02a4dca863aa7354fc76))\n  - 修复条件导出下无法引入package.json的BUG ([c493629](https://github.com/vuejs/vite/commit/c4936290380891353de0581e432389310147a8e0)), closes [#227](https://github.com/vuejs/vite/issues/227)\n  - 不应该更换依赖包内的字符串 ([b96ed68](https://github.com/vuejs/vite/commit/b96ed689970a1c0ab87f21c7cdf7d72a12c493c2))\n"
  },
  {
    "path": "531-540/531-540.md",
    "content": "# 531 - f448ffe v0.16.7\n\nrelease vite v0.16.7\n\n\n\n# 532 - b5ddcdc [#239](https://github.com/vitejs/vite/pull/239) 为#230的defaultRequestToFile添加optimizeNode的判断 & 添加测试\n\n用moment包作为测试例子，playgrond依赖link。\n\n```typescript\nconst defaultRequestToFile = (publicPath: string, root: string): string => {\n  if (moduleRE.test(publicPath)) {\n    const id = publicPath.replace(moduleRE, '')\n    // try to resolve from optimized modules\n    const optimizedModule = resolveOptimizedModule(root, id)\n    if (optimizedModule) {\n      return optimizedModule\n    }\n    // ...  \n  }\n  // ...  \n}\n```\n\n\n\n# 533 - fa6b56d 重构，去除`resolvePostCssConfig.ts`\n\n改动部分：\n\n- `src/node/utils/index.ts`：去除`export * from './resolvePostCssConfig'`\n- `src/node/utils/cssUtils.ts`：`resolvePostCssConfig.ts`下的所有逻辑，均转移到此\n\n> resolvePostCssConfig.ts的功能为加载postcss.config\n\n\n\n# 534 - 0a1d2ac 重构，对于条件导出有更好的方法\n\n改动部分：\n\n- `src/node/resolver.ts`：去除因条件导出而无法引入`package.json`的预备方法；`resolveNodeModuleFile`中去除添加后缀拓展名称的功能，因为在`529`中有分析，后缀名称会被`resolver.requestToFile`添加（详 **改动一**）\n- `package.json`：添加resolver包依赖\n\n### 改动一\n\n以下没有`package.json`的逻辑已经被去除。\n\n```typescript\nexport function resolveNodeModule(\n  root: string,\n  id: string\n): NodeModuleInfo | undefined {\n  # ----\n  if (!pkgPath) {\n    // if the above resolve failed, it's either the package is not installed,\n    // or the package has explicit exports field preventing us from resolving\n    // its package.json. Try to resovle the package.json's path by sniffing\n    // the node_modules in the path.\n  try {\n      const entryPath = resolveFrom(root, id)\n      if (entryPath) {\n        const moduleIndex = entryPath.lastIndexOf(path.join(`node_modules`, id))\n        if (moduleIndex > 0) {\n          pkgPath = path.join(\n            entryPath.slice(0, moduleIndex),\n            'node_modules',\n            id,\n            'package.json'\n          )\n        }\n      }\n    } catch (e) {}\n  }\n  # ----\n}\n\n// 简化为resolveFrom\nexport function resolveNodeModuleFile(\n  root: string,\n  id: string\n): string | undefined {\n  const cached = nodeModulesFileMap.get(id)\n  if (cached) {\n    return cached\n  }\n  try {\n    const resolved = resolveFrom(root, id)\n    nodeModulesFileMap.set(id, resolved)\n    return resolved\n  } catch (e) {\n    // error will be reported downstream\n  }\n}\n```\n\n\n\n# 535 - c2c9c43 重构，帮模块下的`deep import`句子添加上`?import`参数标记的逻辑，被迁移进`resolver.ts`的`resolveBareModule`中\n\n改动部分：\n\n- `src/node/resolver.ts`：resolveBareModule在检测到模块深导入后，会添加上`?import`\n\n> 有后缀且非`/\\.(?:(?:j|t)sx?|vue)$|\\.mjs$/`才会添加`?import`，包中的相对import也可触发。\n\n\n\n# 536 - aaf61f4 正确添加后缀拓展名\n\n改动部分：\n\n- `src/node/resolver.ts`：`resolveExt`从`path.extname`改为`fs.statSync(file).isFile`；`resolveBareModule`改名为`resolveBareModuleRequest`（详 **改动一**）\n- `src/node/server/serverPluginModuleRewrite.ts`：处理模块相对路径的逻辑修改（详 **改动二**）\n\n### 改动一\n\n封装了isFile方法，内部调用`path.statSync`。所有使用`path.extname`检测文件是否存在的都统一修改为`isFile`。\n\n```typescript\nconst isFile = (file: string): boolean => {\n  try {\n    return fs.statSync(file).isFile()\n  } catch (e) {\n    return false\n  }\n}\n```\n\n### 改动二\n\n不可以修复529的改动。尤大似乎觉得`bareImportRE`能返回完整路径，包内的相对路径不能自动寻找`index.js`，依旧不能修复问题。\n\n```typescript\nexport const resolveImport = (\n  root: string,\n  importer: string,\n  id: string,\n  resolver: InternalResolver,\n  timestamp?: string\n): string => {\n  id = resolver.alias(id) || id\n  if (bareImportRE.test(id)) {\n    // directly resolve bare module names to its entry path so that relative\n    // imports from it (including source map urls) can work correctly\n    return `/@modules/${resolveBareModuleRequest(root, id, importer)}`\n  } else {\n    // 1. relative to absolute\n    //    ./foo -> /some/path/foo\n    let { pathname, query } = resolveRelativeRequest(importer, id)\n\n    // 2. if this is a relative import between files under /@modules/, preserve\n    // them as-is\n    // 尤大似乎觉得`bareImportRE`能返回完整路径，包内的相对路径不能自动寻找`index.js`，依旧不能修复问题。\n    if (moduleRE.test(pathname)) {\n      return pathname\n    }\n\n    // 3. resolve extensions.\n    const file = resolver.requestToFile(pathname)\n    pathname = '/' + slash(path.relative(root, file))\n\n    // 4. mark non-src imports\n    // 非src资源标记符 import  \n    const ext = path.extname(pathname)\n    if (ext && !jsSrcRE.test(pathname)) {\n      query += `${query ? `&` : `?`}import`\n    }\n\n    // 5. force re-fetch dirty imports by appending timestamp\n    if (timestamp) {\n      const dirtyFiles = hmrDirtyFilesMap.get(timestamp)\n      // only force re-fetch if this is a marked dirty file (in the import\n      // chain of the changed file) or a vue part request (made by a dirty\n      // vue main request)\n      if ((dirtyFiles && dirtyFiles.has(pathname)) || /\\.vue\\?type/.test(id)) {\n        query += `${query ? `&` : `?`}t=${timestamp}`\n      }\n    }\n    return pathname + query\n  }\n}\n```\n\n\n\n# 537 - 03466c8 为条件导出添加测试\n\n只要引入显示不同的内容测试即可成功。\n\n```json\n{\n    \"exports\": {\n        \".\": {\n            \"import\": \"files/index.mjs\",\n            \"require\": \"files/index.js\"\n        }\n    }\n}\n```\n\n> 也是link\n\n\n\n# 538 - 7a4e8a9 测试：在对html文件的hmr测试中，添加上`page.waitForNavigation`\n\n因为改动后会刷新页面，所以添加上`page.waitForNavigation`等待页面加载完毕再进行验证。\n\n> 只要触发full-reload，都需要添加。\n>\n> 目前没有别的测试会触发`full-reload`。\n\n\n\n# 539 - 3d2b2dd changelog\n\n## [0.16.8](https://github.com/vuejs/vite/compare/v0.16.7...v0.16.8) (2020-05-23)\n\n### Bug Fixes\n\n- defaultRequestToFile 应该包括依赖优化后的模块 ([#239](https://github.com/vuejs/vite/issues/239)) ([b5ddcdc](https://github.com/vuejs/vite/commit/b5ddcdcc65f62bf3fd50e487dc2d9bfa61624539))\n- 正确添加后缀拓展名称 ([aaf61f4](https://github.com/vuejs/vite/commit/aaf61f4d0d6843d0b34c9c75c4dec8a95e95b9d1)), closes [#237](https://github.com/vuejs/vite/issues/237)\n\n\n\n# 540 - 5326eb1 v0.16.8\n\nrelease v0.16.8\n\n"
  },
  {
    "path": "541-550/541-550.md",
    "content": "# 541 - bf2b2a9 public需要被复制到dist目录下，而不是dist/public\n\n改动部分：\n\n- `src/node/build/buildPluginAsset.ts`：`resolveAsset`生成的路径去除`/public`\n- `src/node/build/index.ts`：复制`public`下的文件到输出的文件目录下，改动为去除`/public`这一层目录\n\n\n\n# 542 - fd68ecf index.html资源路径改写后需要添加上双引号\n\n改动部分：\n\n- `src/nodr/build/buildPluginHtml.ts`：`compileHtml`为资源路径改写添加上双引号\n\n![1](1.png)\n\n\n\n# 543 - 72edbc0 changelog\n\n## [0.16.9](https://github.com/vuejs/vite/compare/v0.16.8...v0.16.9) (2020-05-23)\n\n### Bug Fixes\n\n- public的资源需要复制到dist下作为根目录 ([bf2b2a9](https://github.com/vuejs/vite/commit/bf2b2a9c7d66b001260e60d825ae72e8c3e0c301))\n- html的url改写需要添加上双引号 ([fd68ecf](https://github.com/vuejs/vite/commit/fd68ecfa5c5e74a1a463ed5c91b9fecba356f846))\n\n\n\n# 544 - 3f0aff9 v0.16.9\n\nrelease v0.16.9\n\n\n\n# 545 - 1f4518b 构建遇到(j|t)sx需要加载`vueJsxCompat.js`脚本\n\n改动部分：\n\n- `src/node/build/buildPluginEsbuild.ts`：防止`vueJsxPublicPath`被改写 & 加载`vueJsxCompat.js`\n\n```typescript\n  return {\n    name: 'vite:esbuild',\n\n    resolveId(id) {\n      // 防止被改写  \n      if (id === vueJsxPublicPath) {\n        return vueJsxPublicPath\n      }\n    },\n\n    load(id) {\n      if (id === vueJsxPublicPath) {\n        return fs.readFileSync(vueJsxFilePath, 'utf-8')\n      }\n    },\n  }\n```\n\n#### shim是什么？\n\nVue响应式原理中说道：Object.defineProperty是Es5中无法shim的特性，那么这里的shim是什么呢？\nshim可以将新的API引入到旧的环境中，而且仅靠就环境中已有的手段实现。\n\n文章中说的意思就是，Object.defineProperty这个特性是无法使用低级浏览器中的方法来实现的，所以Vue不支持IE8以及更低版本的浏览器。\n\nes5-shim可以让一些低级的浏览器支持最新的ecmascript5的一些特性。支持浏览器或node.js，已经测试的功能见https://github.com/es-shims/es5-shim\n\n> 优先级低于`vite:resolve`，有可能被用户的resolver.requestToFile或者resolver.alias改写到，从而触发不了。\n\n\n\n\n\n# 546 - 7c4b64c 构建下需要对`/vite`下的文件改写字符串\n\n改动部分：\n\n- `src/node/build/index.ts`：`createReplacePlugin`的匹配规则中，新增`id.startsWith('/vite')`\n\n> 现在vite下的特殊文件，就只有`vueJsxCompat.js`，通过特殊路径`'/vite/jsx'`即可引入\n\n\n\n# 547 - 4cb0f76 changelog\n\n## [0.16.10](https://github.com/vuejs/vite/compare/v0.16.9...v0.16.10) (2020-05-24)\n\n### Bug Fixes\n\n- `__DEV__`的字符串替换也需要应用到vite的模块中  ([7c4b64c](https://github.com/vuejs/vite/commit/7c4b64c47ae5271fe262796e1459ff02baf132e2))\n- vue中为了可以使用react的vnode方法，需要引入`vueJsxCompat.js`脚本兼容 ([1f4518b](https://github.com/vuejs/vite/commit/1f4518b69b6d6d4afdb485570ed795fe4f557a77))\n\n\n\n# 548 - 0eb66a8 0.16.10\n\nrelease vite v0.16.10\n\n\n\n# 549 - 528aad6 #256 为资源id删除hash fragment 和 参数\n\n改动部分：\n\n- `src/node/build/buildPluginAssets.vue`：`resolveAsset`中对`id`调用`cleanUrl`删除`hash`或者query\n\n```typescript\nexport const resolveAsset = async (\n  id: string,\n  root: string,\n  publicBase: string,\n  assetsDir: string,\n  inlineLimit: number\n): Promise<AssetCacheEntry> => {\n  id = cleanUrl(id)\n  // ...\n}\n\nexport const queryRE = /\\?.*$/\nexport const hashRE = /\\#.*$/\n\nexport const cleanUrl = (url: string) =>\n  url.replace(hashRE, '').replace(queryRE, '')\n```\n\n>如果添加参数，rollup将会报错找不到该文件，构建失败。\n\n\n\n# 550 - 8f7ee38 #253 修复没有使用的css会触发hmr的问题\n\n改动部分：\n\n- `src/node/server/serverPluginCss.ts`：不存在于`processedCSS`或`srcImportMap`的文件将不触发hmr（详 **改动一**）\n\n### 改动一\n\n只要是`.css`都会发送`style-update`，client端检测到反而会把没有使用的css脚本给添加上。\n\n![2](2.png)\n\n"
  },
  {
    "path": "551-560/551-560.md",
    "content": "# 551 - db8b6b2 #235 支持自定义`NODE_ENV`，支持`--mode`\n\n改动部分：\n\n- `src/node/cli.ts`：新增`--mode | -m`，默认serve模式下`development`，build模式下`production`\n- `src/node/config.ts`：新增`loadEnv`方法，加载`env.XXXX`，默认优先加载`.env`文件（详 **改动二**）\n\n> 之前支持的仅仅是`.env`，这次PR主要是可以通过命令行配置mode，来加载`.env.${mode}`文件来动态配置\n\n### 改动二\n\n检测顺序：`.env` -> `.env.local` -> `.env.${mode}` -> `.env.${mode}.local`\n\n```typescript\n// load environment variables\n    const env = loadEnv(mode, cwd)\n    debug(`env: %O`, env)\n    config.env = env\n\nfunction loadEnv(mode: string, cwd: string): Record<string, string> {\n  debug(`env mode: ${mode}`)\n  const { resolve } = path\n  \n  // 检测的现后顺序\n  const envFiles = [\n    /** default file */ resolve(cwd, '.env'),\n    /** local file */ resolve(cwd, `.env.local`),\n    /** mode file */ resolve(cwd, `.env.${mode}`),\n    /** mode local file */ resolve(cwd, `.env.${mode}.local`)\n  ]\n\n  const env: Record<string, string> = {}\n  for (const file of envFiles) {\n    if (fs.existsSync(file) && fs.statSync(file).isFile()) {\n      const result = dotenv.config({\n        debug: !!process.env.DEBUG,\n        path: file\n      })\n      if (result.error) {\n        throw result.error\n      }\n      Object.assign(env, result.parsed)\n    }\n  }\n\n  return env\n}\n```\n\n\n\n# 552 - cf5de5b #243 重构 vue hmr，使用hot.accept来替换\n\n**underfin**: 我这样做是因为我认为让内置Vue进程的逻辑使用插件来实现是很有用的。我认为Vue模块的HMR应该由HMR API实现，而不是使用内部逻辑实现。\n\n改动部分：\n\n- `src/client/client.ts`：去除`vue-reload`、`vue-rerender`和`vue-style-update`事件，不再调用`updateStyle`，只做一个api输出。\n- `src/node/server/serverPlginCss.ts`：转换import类型的css所植入的js代码被封装为`codegenCss`方法中。（详 **改动二**）\n- `src/node/server/serverPluginHmr.ts`：添加`vueReload`（实际也是`js-update`）方法，`<script>`、`<style>`（`module`和`scope`）都会触发`vueReload`，`<style>`内容的不同会触发`style-update`（详 **改动三**）\n- `src/node/server/serverPluginModuleRewrite.ts`：`rewriteFileWithHMR`包含`SFC VUE`。\n- `src/node/server/serverPluginVue.ts`：`<style>`被转换为利用`codegenCss`注入; `SFC VUE`的主文件被注入`hot.accept(m) => __VUE_HMR_RUNTIME__.reload(\"${id}\", m.default)`和`hot.accept(${JSON.stringify(templateRequest)}, (m) => __VUE_HMR_RUNTIME__.rerender(\"${id}\", m.render)`; （详 **改动五**）\n\n> “隐式位置” \\b，匹配这样的位置：它的前一个“显式位置”字符和后一个“显式位置”字符不全是 \\w。\n\n### 改动二\n\n科普一下如果单纯引入css会出现什么结果，统一转换为js代码，出现BUG，并没有正确引入：\n\n![1](1.png)\n\n### 改动三 `handleVueReload`\n\n所有事件均变为触发`js-update`。\n\n```typescript\nasync function handleVueReload(\n  file: string,\n  timestamp: number = Date.now(),\n  content?: string\n) {\n  const publicPath = resolver.fileToRequest(file)\n  const cacheEntry = vueCache.get(file)\n\n  debugHmr(`busting Vue cache for ${file}`)\n  vueCache.del(file)\n\n  const descriptor = await parseSFC(root, file, content)\n  if (!descriptor) {\n    // read failed\n    return\n  }\n\n  const prevDescriptor = cacheEntry && cacheEntry.descriptor\n  if (!prevDescriptor) {\n    // the file has never been accessed yet\n    debugHmr(`no existing descriptor found for ${file}`)\n    return\n  }\n\n  // check which part of the file changed\n  let needRerender = false\n\n  const vueReload = () => {\n    send({\n      type: 'js-update',\n      path: publicPath,\n      changeSrcPath: publicPath,\n      timestamp\n    })\n    console.log(\n      chalk.green(`[vite:hmr] `) +\n        `${path.relative(root, file)} updated. (reload)`\n    )\n  }\n\n  if (!isEqual(descriptor.script, prevDescriptor.script)) {\n    vueReload()\n    return\n  }\n\n  if (!isEqual(descriptor.template, prevDescriptor.template)) {\n    needRerender = true\n  }\n\n  let didUpdateStyle = false\n  const styleId = hash_sum(publicPath)\n  const prevStyles = prevDescriptor.styles || []\n  const nextStyles = descriptor.styles || []\n\n  // css modules update causes a reload because the $style object is changed\n  // and it may be used in JS. It also needs to trigger a vue-style-update\n  // event so the client busts the sw cache.\n  if (\n    prevStyles.some((s) => s.module != null) ||\n    nextStyles.some((s) => s.module != null)\n  ) {\n    vueReload()\n    return\n  }\n\n  if (prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped)) {\n    needRerender = true\n  }\n\n  // only need to update styles if not reloading, since reload forces\n  // style updates as well.\n  nextStyles.forEach((_, i) => {\n    if (!prevStyles[i] || !isEqual(prevStyles[i], nextStyles[i])) {\n      didUpdateStyle = true\n      send({\n        type: 'style-update',\n        path: `${publicPath}?type=style&index=${i}`,\n        timestamp\n      })\n    }\n  })\n\n  // stale styles always need to be removed\n  prevStyles.slice(nextStyles.length).forEach((_, i) => {\n    didUpdateStyle = true\n    send({\n      type: 'style-remove',\n      path: publicPath,\n      id: `${styleId}-${i + nextStyles.length}`,\n      timestamp\n    })\n  })\n\n  if (needRerender) {\n    send({\n      type: 'js-update',\n      path: publicPath,\n      changeSrcPath: `${publicPath}?type=template`,\n      timestamp\n    })\n  }\n\n  if (needRerender || didUpdateStyle) {\n    let updateType = needRerender ? `template` : ``\n    if (didUpdateStyle) {\n      updateType += ` & style`\n    }\n    console.log(\n      chalk.green(`[vite:hmr] `) +\n        `${path.relative(root, file)} updated. (${updateType})`\n    )\n  }\n}\n```\n\n### 改动五`SFCMAIN`\n\n对于`SFC`主文件，注入：\n\n```typescript\n  code += `\\n if (__DEV__) {\n  hot.accept((m) => {\n    __VUE_HMR_RUNTIME__.reload(\"${id}\", m.default)\n  })\n}`\n```\n\n对于`<template>`，注入：\n\n```typescript\n  if (descriptor.template) {\n    const templateRequest = publicPath + `?type=template`\n    code += `\\nimport { render as __render } from ${JSON.stringify(\n      templateRequest\n    )}`\n    code += `\\n__script.render = __render`\n    code += `\\n if (__DEV__) {\n  hot.accept(${JSON.stringify(templateRequest)}, (m) => {\n    __VUE_HMR_RUNTIME__.rerender(\"${id}\", m.render)\n  })\n}`\n  }\n```\n\n对于`<style>`的获取，注入：\n\n```typescript\nlet code =\n  `import { updateStyle } from \"${hmrClientPublicPath}\"\\n` +\n  `const css = ${JSON.stringify(css)}\\n` +\n  `updateStyle(${JSON.stringify(id)}, css)\\n`\nif (modules) {\n  code += `export default ${JSON.stringify(modules)}`\n} else {\n  code += `export default css`\n}\n```\n\n> 不做全部的详解，本质就是原本更新逻辑，但使用了hot监听文件改动的方式触发。\n\n\n\n# 553 - 3486d21 修复css监听文件变动的过滤器在windows中无效的问题\n\n`file -> slash(file)`，统一不同系统的路径符号。\n\n```typescript\nwatcher.on('change', (file) => {\n  /** filter unused files */\n  if (\n    !Array.from(processedCSS.keys()).some((processed) =>\n      file.includes(processed)\n    ) &&\n    !srcImportMap.has(file)\n  ) {\n    return debugCSS(\n      `${basename(file)} has changed, but it is not currently in use`\n    )\n  }\n}\n```\n\n\n\n# 554 - 8816d3b 当包里的`package.json`没有明确入口将输出更多信息\n\n改动部分：\n\n- `src/node/depOptimizer.ts`：没有`pkgInfo.entryFilePath`或`entryFilePath`不存在，将不被纳入依赖优化中。\n- `src/node/resolver.ts`：`resolveBareModuleRequest`，如没有`pkgInfo.entry`，则返回原本`id`。\n\n> entry是什么有什么用？@module/模块id/入口路径，resolve包能直接识别返回路径。\n\n\n\n# 555 - c239067 fix [#251](https://github.com/vitejs/vite/issues/251) history fallback\n\n`await send(ctx, '/index.html')` -> `await send(ctx, 'index.html', { root })`\n\n> [#251](https://github.com/vitejs/vite/issues/251)想要的是进入到src/index.html吧... 这个改动也没有改好\n\n[HTML5 API——无刷新更新地址 history.pushState/replaceState 方法](https://www.cnblogs.com/maorongmaomao/archive/2012/02/20/2359341.html)\n\n\n\n# 556 - 146a49d 修复`<link/> css`引入错误的问题 [#252](https://github.com/vitejs/vite/issues/252)\n\n改动部分：\n\n- `src/client/client.ts`：`style-update`只要触发都会带上`import`参数，带上import的会被`codegenCss`\n- `src/node/server/serverPluginCss.ts`：纯引入`css`，不需要使用`codegenCss`。（小BUG，上次PR不小心改到了）\n\n\n\n# 557 - 3fc05d5 changelog\n\n## [0.16.11](https://github.com/vuejs/vite/compare/v0.16.10...v0.16.11) (2020-05-25)\n\n### Bug Fixes\n\n- 修复history fallback (fix [#251](https://github.com/vuejs/vite/issues/251)) ([c239067](https://github.com/vuejs/vite/commit/c239067969677bc09ad809baf02495072a38b2ff))\n- 修复纯css引入的BUG (fix [#252](https://github.com/vuejs/vite/issues/252)) ([146a49d](https://github.com/vuejs/vite/commit/146a49d78bd8225f846db8baa4adfa604d4cbf4a))\n- 兼容windows下css过滤器 ([3486d21](https://github.com/vuejs/vite/commit/3486d2117faac0d83bc093f0c8c21b783b8f9f2d))\n- 对于依赖包存在的入口问题，将给予更多的提示，没有entry默认返回id ([8816d3b](https://github.com/vuejs/vite/commit/8816d3bca6aef8df11f70f934031178accde5163)), closes [#247](https://github.com/vuejs/vite/issues/247)\n- 没有使用的css应该跳过hmr([#253](https://github.com/vuejs/vite/issues/253)) ([8f7ee38](https://github.com/vuejs/vite/commit/8f7ee38965327cf15dbb4f2d6f3db6e4b642b635))\n- **build:** 应该对id删除hash和参数 ([#256](https://github.com/vuejs/vite/issues/256)) ([528aad6](https://github.com/vuejs/vite/commit/528aad6b66c407e70bab2012d24a5ca0df30edd5))\n\n### Features\n\n- 支持`--mode` ([#235](https://github.com/vuejs/vite/issues/235)) ([db8b6b2](https://github.com/vuejs/vite/commit/db8b6b23d6e230505b48890cc95e0d8642e98804))\n\n\n\n# 558 - 987843e v0.16.11\n\nrelease v0.16.11\n\n\n\n# 559 - 5589fa3 图片静态资源支持webp格式\n\n改动部分：\n\n- `src/node/utils/pathUtils.ts`：`const imageRE = /\\.(png|jpe?g|gif|svg|ico|webp)(\\?.*)?$/`\n\n![2](2.png)\n\n> **WebP** is 是由 Google 公司开发的一种可提供有损和无损压缩的图片格式，支持透明度，目前已支持动图。\n\n\n\n# 560 - 4fceaea lookupFile寻找`.env.XXX`\n\n改动部分：\n\n- `src/node/config.ts`：`loadEnv`使用`lookupFile`寻找`.env`文件路径。\n\n```typescript\nfunction loadEnv(mode: string, root: string): Record<string, string> {\n  debug(`env mode: ${mode}`)\n  const envFiles = [\n    /** default file */ `.env`,\n    /** local file */ `.env.local`,\n    /** mode file */ `.env.${mode}`,\n    /** mode local file */ `.env.${mode}.local`\n  ]\n\n  const env: Record<string, string> = {}\n  for (const file of envFiles) {\n    const path = lookupFile(root, [file], true)\n    if (path) {\n      const result = dotenv.config({\n        debug: !!process.env.DEBUG,\n        path\n      })\n      if (result.error) {\n        throw result.error\n      }\n      Object.assign(env, result.parsed)\n    }\n  }\n\n  return env\n}\n```\n\n> [lookupFile](https://github.com/Kingbultsea/vite-analysis/blob/da52013f4bbb34b49fe4d80d250f4477bfc54a3e/411-420/411-420.md#%E6%94%B9%E5%8A%A8%E4%BA%8C-lookupfile%E6%96%B9%E6%B3%95)\n\n"
  },
  {
    "path": "561-570/561-570.md",
    "content": "# 561 - 84fcfb6 import改写需要考虑非项目下的路径\n\n改动部分：\n\n- `src/node/server/serverPluginModuleRewrite.ts`：帮文件添加后缀，需要考虑到路径本身是不在`vite cli`运行下的路径。（详 **改动一**）\n\n### 改动一\n\n```typescript\nconst indexRE = /\\/index\\.\\w+$/\nconst indexRemoveRE = /\\/index(\\.\\w+)?$/\n\nexport const resolveImport = (\n  root: string,\n  importer: string,\n  id: string,\n  resolver: InternalResolver,\n  timestamp?: string\n): string => {\n  // ...  \n    \n  // 旧代码 3. resolve extensions.\n  // 只考虑到项目下的相对路径  \n  const file = resolver.requestToFile(pathname)\n  pathname = '/' + slash(path.relative(root, file))\n  \n  // 修正后的代码 3. resolve extensions.\n  const file = slash(resolver.requestToFile(pathname))\n  const resolvedExt = path.extname(file) // 取得真正的文件后缀\n  if (resolvedExt !== path.extname(pathname)) {  // 对比路径的后缀名称是否一样\n    const indexMatch = file.match(indexRE)\n    // 如果不为真正的resolvedExt，如resolvedExt: ts 原本import为.abc\n    if (indexMatch) {\n      // 替换掉原本路径的后缀\n      pathname = pathname.replace(indexRemoveRE, '') + indexMatch[0]\n    } else {\n      // 添加后缀  \n      pathname += resolvedExt\n    }\n  }\n    \n  // ...  \n}\n```\n\n#### 什么情况下`pathname`不等于`resolver.requestToFile`处理后的？\n\n如果用户配置了`.abc`的路径一律转换为`.ts`。\n\n\n\n# 562 - a52ca51 changelog\n\n## [0.16.12](https://github.com/vuejs/vite/compare/v0.16.11...v0.16.12) (2020-05-25)\n\n### Bug Fixes\n\n- 修复了根目录外文件的重写扩展名追加问题 ([84fcfb6](https://github.com/vuejs/vite/commit/84fcfb66ecd9822ebb9dd56505332acce20da568))\n- 使用向上搜索环境文件，lookupFile ([4fceaea](https://github.com/vuejs/vite/commit/4fceaea1b60ba71f954796dfc601e91300344d3f))\n\n### Features\n\n- 支持webp的静态资源 ([5589fa3](https://github.com/vuejs/vite/commit/5589fa3ea51f5442083eb4a31844e23386c89af4))\n\n\n\n# 563 - ca95925 v0.16.12\n\nrelease v0.16.12\n\n\n\n# 564 - a68bfc3 重新设计`HMR API`\n\n破坏性改动，HMR API将会被重新设计。\n\n- 所有HMR API现在将放置在`import.meta.hot`中，HMR API需要在`if(import.meta.hot) {}`里调用。\n- `import.meta.hot.accept()`只允许通过监听自身变动来更新自己。\n- `import.meta.hot.acceptDeps()`现在被用于接受依赖。\n- `import.meta.hot.data` **todo**\n- `import.meta.hot.dispose()` ：[294 - 使用`hot.dispose`，`callback`调用栈为改动前的值。](https://github.com/Kingbultsea/vite-analysis/blob/ea4fb552986c95f4da44839e03a00192ce424139/291-300/291-300.md#294---e5cf447-hmr%E5%8A%9F%E8%83%BD%E6%94%AF%E6%8C%81hotdispose) \n- `import.meta.hot.decline`：让`importer`纳入`full-reload`，从而不触发`js-update`\n\n改动部分：\n\n- `hmr.d.ts`：`hot`被丢进了`ImportMeta`接口（详 **改动一**）\n- `playground/testHmrManual.js`：测试用例（详 **改动二**）\n- `playground/testHmrManualDep.js`：测试用例（详 **改动三**）\n- `src/clinet/client.ts`：`jsHotModuleMap` -> `hotModulesMap`; `jsDisposeMap` -> `disposeMap`; 新增`createHotContext`；（详 **改动四**）\n- `src/node/build/buildPluginResolve.ts`：去除`hmrClientId`的代码兼容（就是假代码，防止用户没有正常调用导致构建后的报错）\n- `src/node/build/index.ts`：替换`import.meta.hot`为`false`，防止构建下触发了`server`的代码\n- `src/node/server/serverPluginHmr.ts`：删除了很多代码，改用为判断`import.meta.hot`的`estree`，改动很大，可以查看**改动七**。（详 **改动七**）\n- `src/node/server/serverPluginModuleRewrite.ts`：改由判断`import.meta`触发`rewriteFileWithHMR`，**注意**，对于`.vue`也会被改写，`.vue`用了`hmr api`，之前的重构有说。\n- `src/node/server/serverPluginVue.ts`：`handleVueReload`被迁移到这里，`import.meta.hot = createHotContext(${JSON.stringify(importer)})`的初始化，在`rewriteFileWithHMR`中注入，即在`serverPluginModuleRewrite.ts`改写的时候注入。\n\n### 改动一\n\n在后面的改动中，会详细讲解，现在只是列举了方法名称。\n\n```typescript\ndeclare interface ImportMeta {\n  hot: {\n    data: any\n\n    accept(): void\n    accept(cb: (mod: any) => void): void\n\n    acceptDeps(dep: string, cb: (mod: any) => void): void\n    acceptDeps(deps: string[], cb: (mods: any[]) => void): void\n\n    dispose(cb: (data: any) => void): void\n    decline(): void\n    invalidate(): void\n\n    on(event: string, cb: (...args: any[]) => void): void\n  }\n}\n```\n\n### 改动二\n\n```typescript\nimport './testHmrManualDep'\n\nexport const foo = 1\n\nif (import.meta.hot) {\n  import.meta.hot.accept(({ foo }) => {\n    console.log('(self-accepting)1.foo is now:', foo)\n  })\n\n  import.meta.hot.accept(({ foo }) => {\n    console.log('(self-accepting)2.foo is now:', foo)\n  })\n\n  import.meta.hot.dispose(() => {\n    console.log(`foo was: ${foo}`)\n  })\n\n  import.meta.hot.acceptDeps('./testHmrManualDep.js', ({ foo }) => {\n    console.log('(single dep) foo is now:', foo)\n  })\n\n  import.meta.hot.acceptDeps(['./testHmrManualDep.js'], (modules) => {\n    console.log('(multiple deps) foo is now:', modules[0].foo)\n  })\n}\n```\n\n### 改动三\n\n```typescript\nexport const foo = 1\n\nif (import.meta.hot) {\n  const data = import.meta.hot.data\n  console.log(`(dep) foo from dispose: ${data.fromDispose}`)\n\n  import.meta.hot.dispose((data) => {\n    console.log(`(dep) foo was: ${foo}`)\n    data.fromDispose = foo * 10\n  })\n}\n```\n\n### 改动四 `createHotContext`\n\n原本的`accept`用法和现在`acceptDeps`一样，现在的`accept`只不过是封装的`acceptDeps`；\n\n#### 为什么id会被设置了两次？\n\n我认为是没有必要的，代码上完全没有使用`HotModule.id`。\n\n```typescript\nexport const createHotContext = (id: string) => {  \n  if (!dataMap.has(id)) {\n    dataMap.set(id, {})\n  }\n\n  const hot = {\n    get data() {\n      return dataMap.get(id)\n    },\n\n    accept(callback: HotCallback['fn'] = () => {}) {\n      hot.acceptDeps(id, callback)\n    },\n\n    acceptDeps(\n      deps: HotCallback['deps'],\n      callback: HotCallback['fn'] = () => {}\n    ) {\n      const mod: HotModule = hotModulesMap.get(id) || {\n        id, // 我不知道套两次id的意义\n        callbacks: []\n      }\n      mod.callbacks.push({\n        deps: deps as HotCallback['deps'],\n        fn: callback\n      })\n      hotModulesMap.set(id, mod) // 已经有了id了\n    },\n\n    dispose(cb: (data: any) => void) {\n      disposeMap.set(id, cb)\n    },\n\n    // noop, used for static analysis only\n    decline() {},\n\n    invalidate() {\n      location.reload()\n    },\n\n    // custom events\n    // custom事件，由sever端发送触发，这里是注册  \n    on(event: string, cb: () => void) {\n      const existing = customUpdateMap.get(event) || []\n      existing.push(cb)\n      customUpdateMap.set(event, existing)\n    }\n  }\n\n  return hot\n}\n```\n\n### 改动七\n\n`handleVueReload`与`isEqualBlock`的逻辑被迁移到`src/node/server/serverPluginVue.ts`，因为都是与`vue`强相关。\n\n`jsBoundaries` -> `hmrBoundaries`，因为之前说过只有`Hmr`才会调用`js-update`。\n\n去除了`vueBoundaries`触发`js-update`事件，直接又以下逻辑触发。\n\n```typescript\nwatcher.on('change', (file) => {\n  if (\n    !(\n      file.endsWith('.vue') ||\n      file.endsWith('.css') ||\n      cssPreprocessLangRE.test(file)\n    )\n  ) {\n    // everything except plain .css are considered HMR dependencies.\n    // plain css has its own HMR logic in ./serverPluginCss.ts.\n    handleJSReload(file)\n  }\n})\n```\n\n`walkImportChain`不再分辨`.vue`和`.js`（或者说其余文件）文件的区别，就是说importer不管是什么，只要符合`isHmrAccepted`。\n\n`dirtyFilesMap`是用来**统一**`hmr`更改文件时间和`serverModuleRewrite`改写`import`语句时间。（[传送门](https://github.com/Kingbultsea/vite-analysis/blob/f32926641f27f5b7cfd77d4be596a82a2fd32d17/341-350/341-350.md#%E6%94%B9%E5%8A%A8%E4%BA%8C-1)，时间统一起来了，浏览器缓存会得到利用）\n\n#### `rewriteFileWithHMR`\n\n`estree`字段详解省略，这更多的是对`estree`文档的查阅。\n\n自动注入：\n\n```typescript\n`\nimport { createHotContext } from \"${hmrClientPublicPath}\"\nimport.meta.hot = createHotContext(${JSON.stringify(importer)})\n  `\n```\n\n`import.meta.hot.decline`，调用后设置当前`importer`进`hmrDeclineSet`，当`walkImportChain`检测到`importee`存在于`hmrDeclineSet`，`hmr`事件直接判断为`full-reload`。假如你A脚本import了调用`hmr api`的文件B，同时那个文件B调用了`import.meta.hot.decline()`，那么可以触发`full-reload`代替`js-update`（[传送门 - 不要import使用了**HMR API的脚本**](https://github.com/Kingbultsea/vite-analysis/blob/ca743cb71f4123201f705ca2c73e2d5483801beb/311-320/311-320.md#312---0708279-%E9%87%8D%E6%9E%8497%E4%B8%94%E5%85%A8%E5%B1%80%E6%B3%A8%E5%86%8Cimporter%E4%B8%8Eimportee%E5%85%B3%E7%B3%BB%E5%8D%B3%E8%AE%BE%E7%BD%AEimportermap)）\n\n\n\n# 565 - 86d2143 构建默认`--mode`为`production`\n\nfix: default mode for build API usage\n\n\n\n# 566 - 0ce1eef 遵循用户为 `rollup-plugin-vue` 配置的` css` 模块选项\n\n用户可以通过配置`rollupPluginVueOptions.cssModulesOptions`方法，配置`cssModulesOptions`选项，比如你想自行设置`scpoed`的名称，可以覆盖原有的`generateScopedName`。\n\n```typescript\nrequire('rollup-plugin-vue')({\n      // ...\n      cssModulesOptions: {\n        generateScopedName: (local: string, filename: string) =>\n          `${local}_${hash_sum(filename)}`,\n        ...(options.rollupPluginVueOptions &&\n          options.rollupPluginVueOptions.cssModulesOptions)\n      }\n    })\n```\n\n\n\n# 567 - 301d7a3 [#260](https://github.com/vitejs/vite/pull/260) chore\n\n`src/node/server/serverPluginVue.ts`：SFC更新了哪块的信息需要添加上` & `。\n\n\n\n# 568 - 9503762 破坏性改动，`__BASE__`转换为`process.env.BASE_URL`\n\n`__BASE__`转换为`process.env.BASE_URL`来代替。\n\n\n\n# 569 - 319b37b 支持从 root 引用public目录文件\n\n即原本`<img src=\"./public/icon.svg\"/>`，现在可以用`<img src=\"./icon.svg\"/>`来应用。\n\n改动部分：\n\n- `src/node/resolver.ts`：`defaultRequestToFile`中，如果检测到`publicPath`在`public`目录下存在，则改写为`./public/${publicPath}`。\n- `src/node/build/buildPluginAssets.ts`：`resolveAsset`如果id不存在，则尝试从`public`寻找。\n\n> 静态资源插件`sereverPluginServeStatic`会通过`resolver.requestToFile`把`publicPath`转换为文件磁盘位置。\n\n\n\n\n\n# 570 - ec7401f chore\n\n改动部分：\n\n- `resolver.ts`：`resolveExt`添加了后缀才输出信息。\n\n"
  },
  {
    "path": "571-580/571-580.md",
    "content": "# 571 - dd0205f `esbuild`服务，如果停止，则设置`_service = undefined`\n\n`src/nopde/esbuildService.ts`：`stopService`调用后，需要设置`_service`取消指引。\n\n\n\n# 572 - df526b1 修复`resolveImport`中的添加参数`import`逻辑，即判断`query`不存在再添加`import`参数\n\n- `src/node/server/serverPluginModuleRewrite.ts`：修复`resolveImport`中的添加参数`import`逻辑。\n\nimport句子经过`resolveRelativeRequest(importer, id)`处理后，会被拆分为路径和参数。更改后只有没有任何自定义的参数才会添加`import`。\n\n```typescript\n# 更改前\nconst ext = path.extname(pathname)\nif (ext && !jsSrcRe.test(pathname)) {\n    query += `${query ? '&' : '?'}import`\n}\n\n# 更改后\n// 4. mark non-src imports\nif (!query && path.extname(pathname) && !jsSrcRE.test(pathname)) {\n  query += `?import`\n}\n```\n\n![1](1.png)\n\n\n\n# 573 - 43ccaf7 perf 重新设置回`vue hmr`，即恢复`vue-reload`、`vue-rerender`\n\n改动部分：\n\n- `src/client/client.ts`：恢复`vue-reload`和`vue-rerender`。\n- `src/node/server/serverPluginHmr.ts`：`walkImportChain`检测到`importer`为`.vue`后缀，即添加到`dirtyFiles`中（详 **改动二**）\n- `src/node/server/serverPluginModuleRewrite.ts`：存在于`dirtyFiles`或`SFC`下的`template` | `style`，才为`import`语句添加`timestamp`。\n- `src/node/server/serverPluginVue.ts`：砍掉`<template>`&`<script>`的`import.meta.hot.accept`的代码注入，重新区分`vue-reload`和`vue-rerender`，**注意**，`<style>`依旧使用`HMR API`，即`codegenCss`会注入`import.meta.hot.accept`逻辑。\n\n### 改动二 为什么`.vue`才添加到`dirtyFiles`？\n\n`isHmrAccepted`的`js`也会添加到`dirtyFiles`中，但是普通的`js`脚本不需要时间戳达到删除缓存的效果，因为它会触发的是`reload`事件，使页面刷新。\n\n**改动三**中`SFC`下的`template` | `style`，不会被丢进`dirtyFiles`中，所以要通过判断来添加`timestamp`。\n\n\n\n# 574 - 197499a bump `esbuild`\n\n依赖更新，`esbuild` -> `^0.4.1`\n\n\n\n# 575 - 5435737 fix [#262](https://github.com/vitejs/vite/pull/262) 更好的判断逻辑 & 变量命名 \n\n`src/node/server/serverPluginCss.ts`：`HMR watcher`更好的判断逻辑 & 变量命名 \n\n\n\n# 576 - ca1b551 fix [#263](https://github.com/vitejs/vite/pull/263) `dotenv`假值传递`undefinded`\n\n**yyx**：`dotenv`有些奇怪...\n\n\n\n# 577 - d229a5b [#266](https://github.com/vitejs/vite/pull/266) 更清晰的变量命名\n\n`filename` -> `filePath`\n\n`file` -> `filePath`\n\n\n\n# 578 - d71a06d [#267](https://github.com/vitejs/vite/pull/267) `vite:resolve` `resolveId`钩子等待resolve\n\n**csr632**: `this.resolve` 返回一个`promise`。这个`promise`可能会解析为空。 没有等待这个`promise`，`resolve || { id }`将没有意义。\n\n#### `resolveId`\n\nType: `(source: string, importer: string | undefined) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null}`\n\n#### `syntheticNamedExports`是什么？\n\n设置后，可以使用`export default { a: 123, b: 123 }`这种形式，等于解释为\n\n`export const a = 123; export const b = 123`\n\n> 之前困扰了我很久的问题，我之前想的是莫非返回promise会自动等待结果？（看上面并没有返回promise的类型）即使就算是这样，那`|| { id }`就没有任何意义了。\n>\n> 这块如果不`await`的话，就当作null处理了。\n\n\n\n# 579 - 610a004 [#269](https://github.com/vitejs/vite/pull/267) 修复`onRollupWarning`输出信息被`Ora`覆盖问题\n\n改动部分：\n\n- `src/node/build/index.ts`：为了让`onRollupWarning`能正常输出，需要暂停`Ora`再输出，完毕后再启动`Ora`。\n- `src/node/depOptimizer.ts`：传递`Ora`（也就是`onRollupWarning(spiner)`）\n\n> `(spiner) => () => { spiner }`，可以快速创建作用域。\n\n\n\n# 580 - e67b698 fix [#270](https://github.com/vitejs/vite/pull/270) `isLocal`统一处理vue路径\n\n`src/node/utils/resolveVue`：在`isLocal`的情况下，调用：\n\n```typescript\nvuePath = resolveFrom(\n        root,\n        '@vue/runtime-dom/dist/runtime-dom.esm-bundler.js'\n      )\n```\n\n> 话说我clone了项目，构建后启动服务，完全没有问题... \n>\n> 可能是因为他构建后，没有进入`dist`文件下，直接http-server架起服务？我就是这样触发到的，浏览器中不能使用`import 'ABC'`这种node中引入模块的方式。（但我相信他并不会有这种操作... 留坑）\n\n"
  },
  {
    "path": "581-590/581-590.md",
    "content": "# 581 - 2e81e64 新增`babelParse.ts`，用于封装`parse`选项 & fix #271 `serverPluginVue`对于`export default`的错误替换\n\n改动部分：\n\n- `src/node/utils/babelParse.ts`：新增，封装`@babel/parser`的`parse`方法提供给`serverPluginHmr.ts`的`rewriteFileWithHMR`方法、`serverPluginVue.ts`的`rewriteDefaultExport`方法。（详 **新增一**）\n- `src/node/server/serverPluginVue.ts`：新增`rewriteDefaultExport`方法，把以前的暴力（我指的暴力是考虑不会很全面，可能出现BUG）正则替换给修正。（详 **改动二**）\n\n### 新增一\n\n逻辑迁移，封装。\n\n```typescript\nimport { parse as _parse } from '@babel/parser'\nimport { Statement } from '@babel/types'\n\nexport function parse(source: string): Statement[] {\n  return _parse(source, {\n    sourceType: 'module',\n    plugins: [\n      // required for import.meta.hot\n      'importMeta',\n      // by default we enable proposals slated for ES2020.\n      // full list at https://babeljs.io/docs/en/next/babel-parser#plugins\n      // this should be kept in async with @vue/compiler-core's support range\n      'bigInt',\n      'optionalChaining',\n      'nullishCoalescingOperator'\n    ]\n  }).program.body\n}\n```\n\n### 改动二\n\n#### 关于正则，转自[ssruoyan - segmentfault](https://segmentfault.com/q/1010000006699973)\n\n`?=`实际上应算是匹配预查。str(?=condition) 后面的应该只能算是匹配的条件。意思就是只有满足condition条件的str才能被匹配到。\n\n`?=`还有一个兄弟叫做`!?`，作用相反，只有在不满足条件的str才能被匹配到。\n\n`?:`就是直接匹配str + conidtion\n\n以上三个都是非获取匹配，意思就是condition匹配到的内容并不会存储到Matches里。\n\n例如：\n\n```awk\nvar str = \"dadiao 2333\";\nstr.match(/dadiao (?:2222|2333|1211)/)  //['dadiao 2333'];\nstr.match(/dadiao (?=2222|2333|1211/) // ['dadiao '];\n\nstr.match(/dadiao (?!=2222)/) //['dadiao ']\n\nstr.match(/dadiao (2222|2333|1211)/) //['dadiao 2333', '2333'];\n```\n\n`content.replace('export default', const __script = )`直接替换，如果遇到的是注释，那就会出现错误：\n\n![1](1.png)\n\n现在更改为：\n\n```typescript\nconst defaultExportRE = /((?:\\n|;)\\s*)export default/\n\n// 改写export default\n// 尝试直接通过正则改写，避免使用babel parse\nlet replaced = content.replace(defaultExportRE, '$1const __script =')\n// 如果脚本以某种方式仍然包含“export default”，则它可能具有\n// 多行注释或模板字符串。回退到完整解析\nif (defaultExportRE.test(replaced)) {\n  replaced = rewriteDefaultExport(content)\n}\ncode += replaced\n\nfunction rewriteDefaultExport(code: string): string {\n  const s = new MagicString(code)\n  const ast = parse(code)\n  ast.forEach((node) => {\n    // 检测node类型 达到替换  \n    if (node.type === 'ExportDefaultDeclaration') {\n      s.overwrite(node.start!, node.declaration.start!, `const __script = `)\n    }\n  })\n  const ret = s.toString()\n  return ret\n}\n```\n\n\n\n# 582 - e86da9e 依赖`@types/lru-cache`提升至`dependencies`\n\n`import { readBody } from 'vite'`，`fsUtils.ts`中引入了`lru-cache`，类型检测如果用户依赖没有安装`@types/lru-cache`将检测错误（未验证的结论）\n\n> 我们的包要从这两个包里暴露出声明文件，因此`browserify-typescript-extension`的用户也需要这些依赖。 正因此，我们使用 `\"dependencies\"`而不是`\"devDependencies\"`，否则用户将需要手动安装那些包。 如果我们只是在写一个命令行应用，并且我们的包不会被当做一个库使用的话，那么我就可以使用 `devDependencies`。\n\nhttps://www.tslang.cn/docs/handbook/declaration-files/publishing.html\n\n\n\n# 583 - ea97bd1 changelog\n\n# [0.17.0](https://github.com/vuejs/vite/compare/v0.16.12...v0.17.0) (2020-05-26)\n\n### Bug Fixes\n\n- rollup等待resolve结果 ([#267](https://github.com/vuejs/vite/issues/267)) ([d71a06d](https://github.com/vuejs/vite/commit/d71a06da04954282896e53e16692590101b82c2e))\n- css cache check 更好的判断逻辑 & 变量命名 ([#262](https://github.com/vuejs/vite/issues/262)) ([5435737](https://github.com/vuejs/vite/commit/5435737d126a2d08e7e950dbf4952fc903574d19))\n- 构建默认--mode为production ([86d2143](https://github.com/vuejs/vite/commit/86d2143e31cba594377da43116c161a87d2d1874))\n- dotenv假值传递undefinded ([#263](https://github.com/vuejs/vite/issues/263)) ([ca1b551](https://github.com/vuejs/vite/commit/ca1b551c541ed3364374652b9b55e9f0e78b0c3c))\n- 依赖`@types/lru-cache`提升至`dependencies` ([e86da9e](https://github.com/vuejs/vite/commit/e86da9e6b56aeaf985ecf590fd775582952279b0))\n- 修复resolveImport中的添加参数import逻辑，即判断query不存在再添加import参数 ([df526b1](https://github.com/vuejs/vite/commit/df526b127e63ff2f52458ea796f9c813880a1a65))\n- 修复onRollupWarning输出信息被Ora覆盖问题 ([#269](https://github.com/vuejs/vite/issues/269)) ([610a004](https://github.com/vuejs/vite/commit/610a00441f8c0faa2e048a0910cf04f9f3810eef))\n- isLocal统一处理vuePath路径 ([e67b698](https://github.com/vuejs/vite/commit/e67b698a9ba18a99cb64f52df61fae176382f9ff)), closes [#270](https://github.com/vuejs/vite/issues/270)\n- 遵循用户为 `rollup-plugin-vue` 配置的` css` 模块选项 ([0ce1eef](https://github.com/vuejs/vite/commit/0ce1eef7bd77eb8468c8b9e6878c2a78167efc4f))\n- `esbuild`服务，如果停止，则设置`_service = undefined` ([dd0205f](https://github.com/vuejs/vite/commit/dd0205f321c57ad0b59813181591dafe1d8d3f90))\n- log中，SFC更新了哪块的信息需要添加上` & `分割 ([#260](https://github.com/vuejs/vite/issues/260)) ([301d7a3](https://github.com/vuejs/vite/commit/301d7a3b151a8fdefd09db0d742c7b6d0ce206db))\n- 新增`babelParse.ts`，用于封装`parse`选项 & fix #271 `serverPluginVue`对于`export default`的错误替换 ([2e81e64](https://github.com/vuejs/vite/commit/2e81e64929d9c2909ff5882b26933ea54a353aab)), closes [#271](https://github.com/vuejs/vite/issues/271)\n\n### Features\n\n- 破坏性改动，`__BASE__`转换为`process.env.BASE_URL` ([9503762](https://github.com/vuejs/vite/commit/9503762e103f304228ceb7d572b17c24ed008501))\n- 支持从 root 直接引用public目录文件 ([319b37b](https://github.com/vuejs/vite/commit/319b37bbf4cef4804b56061ab5354d361c90dacb))\n- **hmr:** 重新设计 HMR API ([a68bfc3](https://github.com/vuejs/vite/commit/a68bfc307dd636d5e1b5d42d6df248da1beea2ff))\n\n### Performance Improvements\n\n- 重新设置回`vue hmr`，即恢复`vue-reload`、`vue-rerender` ([43ccaf7](https://github.com/vuejs/vite/commit/43ccaf77e89ebf219c15aaf12b06a4632beb3968))\n\n### BREAKING CHANGES\n\n- `__BASE__` 作为全局变量，现在已经被移除。可以使用`process.env.BASE_URL` 代替。\n\n- **hmr:** HMR API现在已经被重新设计。\n\n  - 所有HMR API都被迁移至`import.meta.hot`中， HMR API 的代码需要在 `if (import.meta.hot) {}`使用.\n  - `import.meta.hot.accept()` 被改为只接收自身更新\n  - `import.meta.hot.acceptDeps()` 可以用于接收依赖更新\n  - `import.meta.hot.data` todo 空对象，这部分尤大的注释目前还不理解，因为设置的都是`{}`，可以去查看`dataMap`逻辑。（大概猜测是调用栈之前的数据）\n  - `import.meta.hot.dispose()` 调用栈被更新前的值，触发回调。\n  - `import.meta.hot.decline()` 可用于拒绝更新，如果模块在 HMR 更新链中受到影响，可强制整页重新加载。\n  - `import.meta.hot.invalidate()` 可以在接受回调中使用，以有条件地拒绝更新并强制重新加载整页。todo，也是没有看见相关代码，只是一个`location.reload`。\n\n  可以查看`hmr.d.ts` 了解所有HMR API的类型\n\n\n\n# 584 - 87f03a8 v0.17.0\n\nrelease `vite` v0.17.0\n\n\n\n# 585 - 286fb2f 为v0.17.0更新文档\n\n> 直接英文版了，我们可以注意一下的是dispose可以用来删除全局的side effect（其他用法自己定义，反正调用栈是之前的，就是之前的import内调用dispose的回调）\n>\n> 另外一个是public的作用，可以保留文件名称不被hash化，如`robot.txt`这种搜索引擎设置的文件可以被完整保留下来，不需要特别的配置。\n\nFor manual HMR, an API is provided via `import.meta.hot`.\n\nFor a module to self-accept, use `import.meta.hot.accept`:\n\n```js\nexport const count = 1\n\n// the conditional check is required so that HMR related code can be\n// dropped in production\nif (import.meta.hot) {\n  import.meta.hot.accept((newModule) => {\n    console.log('updated: count is now ', newModule.count)\n  })\n}\n```\n\nA module can also accept updates from direct dependencies without reloading itself, using `import.meta.hot.acceptDeps`:\n\n```js\nimport { foo } from './foo.js'\n\nfoo()\n\nif (import.meta.hot) {\n  import.meta.hot.acceptDeps('./foo.js', (newFoo) => {\n    // the callback receives the updated './foo.js' module\n    newFoo.foo()\n  })\n\n  // Can also accept an array of dep modules:\n  import.meta.hot.acceptDeps(['./foo.js', './bar.js'], ([newFooModule, newBarModule]) => {\n    // the callback receives the updated mdoules in an Array\n  })\n}\n```\n\nA self-accepting module, or a module that expects to be accepted by others can use `hot.dispose` to cleanup any persistent side effects created by its updated copy:\n\n```js\nfunction setupSideEffect() {}\n\nsetupSideEffect()\n\nif (import.meta.hot) {\n  import.meta.hot.dispose((data) => {\n    // cleanup side effect\n  })\n}\n```\n\nFor the full API, consult [hmr.d.ts](https://github.com/vitejs/vite/blob/master/hmr.d.ts).\n\nAll **static** path references, including absolute paths, should be based on your working directory structure.\n\n#### The `public` Directory\n\nThe `public` directory under project root can be used as an escape hatch to provide static assets that either are never referenced in source code (e.g. `robots.txt`), or must retain the exact same file name (without hashing).\n\nAssets placed in `public` will be copied to the root of the dist directory as-is.\n\nNote that you should reference files placed in `public` using root absolute path - for example, `public/icon.png` should always be referenced in source code as `/icon.png`.\n\n#### Public Base Path\n\nIf you are deploying your project under a nested public path, simply specify `--base=/your/public/path/` and all asset paths will be rewritten accordingly.\n\n### How is This Different from [Snowpack](https://www.snowpack.dev/)?\n\nBoth Snowpack v2 and Vite offer native ES module import based dev servers. Vite's dependency pre-optimization is also heavily inspired by Snowpack v1. Both projects share similar performance characteristics when it comes to development feedback speed. Some notable differences are:\n\n- Vite was created to tackle native ESM-based HMR. When Vite was first released with working ESM-based HMR, there was no other project actively trying to bring native ESM based HMR to production.\n\n  Snowpack v2 initially did not offer HMR support but added it in a later release, making the scope of two projects much closer. Vite and Snowpack has collaborated on a common API spec for ESM HMR, but due to the constraints of different implementation strategies, the two projects still ship slightly different APIs.\n\n- Vite is more opinionated and supports more opt-in features by default - for example, features listed above like TypeScript transpilation, CSS import, CSS modules and PostCSS support all work out of the box without the need for configuration.\n\n- Both solutions can also bundle the app for production, but Vite uses Rollup while Snowpack delegates it to Parcel/webpack. This isn't a significant difference, but worth being aware of if you intend to customize the build.\n\n\n\n# 586 - e2594df 延迟加载`'@vue/compiler-dom'`，因此可以拥有NODE_ENV变量\n\n改动部分：\n\n- `src/node/build/buildPluginHtml.ts`：利用`require('@vue/compiler-dom')`引入。\n\n> import 的 '@vue/compiler-dom' 只是类型。\n>\n> 延迟加载可以提升性能。\n\n#### 单独的语句可以使用`;`避免报错\n\n![2](2.png)\n\n#### `buildPluginHtml.ts`的todo\n\n```typescript\n// TODO: if there are multiple inline module scripts on the page,\n// they should technically be turned into separate modules, but\n// it's hard to imagine any reason for anyone to do that.\n```\n\n如果有多个`<script type=\"module\">...</script>`，从技术上讲，它们应该变成单独的模块，但是很难想象任何人有什么理由这样做。\n\n\n\n# 587 - 9a44248 `walkImportChain`去除`hasDeadEnd`，直接return结果\n\n```typescript\nconst parentImpoters = importerMap.get(importer)\nif (!parentImpoters) {\n  return true\n} else if (\n  walkImportChain(\n    importer,\n    parentImpoters,\n    hmrBoundaries,\n    dirtyFiles,\n    currentChain.concat(importer)\n  )\n) {\n  return true\n}\n```\n\n\n\n# 588 - ae6b49d `resolveRelativeRequest`改为`resolveImport`完整流程，修复后缀没有自动添加问题\n\n`resolveRelativeRequest`只是单独转换相对路径，`resolveImport`还有一个处理后缀的流程。\n\n![3](3.png)\n\n\n\n# 589 - f94685a chore 修正`ignore dynamic import(XXX)`的控制台信息输出为条件输出\n\n![4](4.png)\n\n\n\n# 590 - 0330b2a 修复588因`resolveImport`带上了`?import`参数导致文件路径不正确的问题\n\n直接使用`cleanUrl`去除参数。\n\n```typescript\nconst importee = cleanUrl(\n  resolveImport(process.cwd(), importer, block.src!, resolver)\n)\n```\n\n"
  },
  {
    "path": "591-600/591-600.md",
    "content": "# 591 - 4508f31 #273 readme 环境变量\n\n### Modes and Environment Variables\n\n> 0.16.7+\n\nThe mode option is used to specify the value of `process.env.NODE_ENV`and the corresponding environment variables files that needs to be loaded.\n\nBy default, there are two modes:\n\n- `development` is used by `vite` and `vite serve`\n- `production` is used by `vite build`\n\nYou can overwrite the default mode used for a command by passing the `--mode` option flag. For example, if you want to use development variables in the build command:\n\n```bash\nvite build --mode development\n```\n\nWhen running `vite`, environment variables are loaded from the following files in your project root:\n\n```\n.env                # loaded in all cases\n.env.local          # loaded in all cases, ignored by git\n.env.[mode]         # only loaded in specified env mode\n.env.[mode].local   # only loaded in specified env mode, ignored by git\n```\n\n\n\n# 592 - b8901d6 chore 构建命令变更\n\n```shell\n$ npx create-vite-app <project-name>\n\n更改为->\nnpm init vite-app <project-name>\n```\n\nThe init command is transformed to a corresponding `npm exec` operation as follows:\n\n- `npm init foo` -> `npm exec create-foo`\n- `npm init @usr/foo` -> `npm exec @usr/create-foo`\n- `npm init @usr` -> `npm exec @usr/create`\n\n即`npm init vite-app` -> `npm exec create-vite-app`\n\nhttps://docs.npmjs.com/cli/v7/commands/npm-init\n\n\n\n# 593 - 3045112 修复`defaultExportRE`正则表达式\n\n换行符需要在开头。\n\n```typescript\n// 修正前：\nconst defaultExportRE = /((?:\\n|;)\\s*)export default/\n\n// 修正后：\nconst defaultExportRE = /((?:^\\n|;)\\s*)export default/\n```\n\n\n\n# 594 - 42526f4 升级依赖`rollup-plugin-vue`\n\n`rollup-plugin-vue: &6.0.0-beta.3`\n\n\n\n# 595 - 0554f06 在`hmr`上下文重新创建时清除陈旧的接受回调 + 避免在嵌套的 `hmr` 更新中获取过时的模块\n\n改动部分：\n\n- `src/client/client.ts`：只要是更新都需要删除`callbacks`，不只是`selfModuleUpdtae`。\n- `src/node/server/serverPlginHmr.ts`：`latestVersionsMap`，`koa`插件中，建立`key: publicpath`，`value: ctx.query.t`关系，提供给`latestVersionsMap`添加`timestamp`\n\n### 谈谈timestamp\n\n- 最初的timestamp是从`handleJSReload`，默认使用`Date.now()`创建，通过HMR事件传给客户端，客户端通过`import('XXXXX?t=timestamp')`又传递给服务器。`handleJSReload`会设置`hmrDirtyFilesMap.set(String(timestamp), dirtyFiles)`，意思是当前脚本更改时间与当前脚本的`importer`们的对应关系，`timestamp -> importers`。\n- 服务器记录`publicPath`与`timestamp`关系，即`latestVersionsMap`。\n- 服务器通过`rewriteImports`，把脚本的`import`语句都加上参数t，即`import { XX } from 'XXX?t=timestamp'`\n\n第三点，不是所有的`import`语句都可以添加上`timestamp`，需要对应`hmrDirtyFilesMap`，即`hmrDirtyFilesMap.get(timestamp)`获取到`importers`，`import`语句存在于`importers`中才可以。\n\n其次是`import`在`latestVersionsMap`有对应的timestamp，才会添加上他对应的timestamp（两个`timestammp`不一定是一样的，第二点的记录是文件最近改动时间）。\n\n**注意第二点，是客户端动态`import`的句子所创造的`publicPath`。**\n\n（不懂吗？可能需要一个图解）\n\n\n\n# 596 - 51da6b5 changelog\n\n## [0.17.1](https://github.com/vuejs/vite/compare/v0.17.0...v0.17.1) (2020-05-27)\n\n### Bug Fixes\n\n- 在`hmr`上下文重新创建时清除陈旧的接受回调 + 避免在嵌套的 `hmr` 更新中获取过时的模块 ([0554f06](https://github.com/vuejs/vite/commit/0554f063f6392fa49da0478fef68c80f10c391fc))\n- 修复`defaultExportRE`正则表达式 ([3045112](https://github.com/vuejs/vite/commit/3045112780a8eeb5b8f455b82939cb00da1eef7d))\n- `walkImportChain`去除`hasDeadEnd`，直接return结果 ([9a44248](https://github.com/vuejs/vite/commit/9a4424822a8d3b3583504b827e1b7089b4319a30))\n- 延迟加载`'@vue/compiler-dom'`，因此可以拥有NODE_ENV变量([e2594df](https://github.com/vuejs/vite/commit/e2594dffe42776cf8c53725d79525fb0b8b08d68))\n- 去除import参数 ([0330b2a](https://github.com/vuejs/vite/commit/0330b2a1f56ea8fa443207c524d817d7de772b56))\n- `resolveRelativeRequest`改为`resolveImport`完整流程，修复后缀没有自动添加问题 ([ae6b49d](https://github.com/vuejs/vite/commit/ae6b49d5bd71a18f917d3a5e57ec3c4b9351da59))\n\n\n\n# 597 - 8cf1f75 v0.17.1\n\nrelease v0.17.1\n\n\n\n# 598 - 9ac63b1 `import.meta.hot` 应该在第一个 `if (import.meta.hot)` 之前注入\n\n是由于取到了`if (import.meta.hot)`的最后一个，现在只需要判断`importMetaConditional`是否存在，存在则不做替换即可。\n\n\n\n# 599 - 6269b7f #284 有些包的入口不带后缀名称，现在帮其默认添加上`.js`\n\n在aaf61f4中，包中的import路径将直接调用`resolveRelativeRequest`处理到完整路径后，直接返回，不添加任何后缀。\n\n> `resolve.sync`将自动寻找文件夹下的`index.js`或者其他后缀，但如果入口文件没有后缀，则无法寻找到（除非配置`isFile`）。\n\n\n\n# 600 - 16ae601 changelog\n\n## [0.17.2](https://github.com/vuejs/vite/compare/v0.17.1...v0.17.2) (2020-05-28)\n\n### Bug Fixes\n\n- 兼容不带扩展名的包条目([6269b7f](https://github.com/vuejs/vite/commit/6269b7f499c96bbe47fc1d8bce7fa77d115e1da6)), closes [#284](https://github.com/vuejs/vite/issues/284)\n- `import.meta.hot` 应该在第一个 `if (import.meta.hot)` 之前注入 ([#285](https://github.com/vuejs/vite/issues/285)) ([9ac63b1](https://github.com/vuejs/vite/commit/9ac63b1320ca929010a9cfd78e3c1a7797bd3a80))\n\n"
  },
  {
    "path": "601-610/601-610.md",
    "content": "# 601 - 4ad4fea v0.17.2\n\nrelease v0.17.2\n\n\n\n# 602 - ed2be13 ci调试 判断import.meta.hot.data存在再输出`console.log`\n\n#### 关于`import.meta.hot.data`的理解\n\n之前对`import.meta.hot.data`有点不理解，实际上他用来传递信息给新的堆栈。\n\n如A.js是一个使用了`import.meta.hot`的脚本，该脚本更新后，浏览器调用`import('A.js')`引入新的模块，`A.js`通过`import.meta.hot.data`可以共享旧的`A.js`内存，总的来说`import.meta.hot.data`只是指向同一个内存块而已。\n\n尤大的目的为，`import.meta.hot.dispose`可以通过`import.meta.hot.data`传递信息给新的脚本。\n\n\n\n# 603 - a4b8d4e chore\n\n`esbuilPlugin` -> `esbuildPlugin`\n\n\n\n# 604 - 24b75a1 ci调试后去除信息\n\nci调试后去除信息，对应于602。\n\n\n\n# 605 - c8ee484 chore readme，阐述`Preact tsx`的用法\n\n#### JSX with React/Preact\n\nThere are two other presets provided: `react` and `preact`. You can specify the preset by running Vite with `--jsx react` or `--jsx preact`.\n\nIf you need a custom JSX pragma, JSX can also be customized via `--jsx-factory` and `--jsx-fragment` flags from the CLI or `jsx: { factory, fragment }` from the API. For example, you can run `vite --jsx-factory=h` to use `h` for JSX element creation calls. In the config (see [Config File](#config-file) below), it can be specified as:\n\n```js\n// vite.config.js\nmodule.exports = {\n  jsx: {\n    factory: 'h',\n    fragment: 'Fragment'\n  }\n}\n```\n\nNote that for the Preact preset, `h` is also auto injected so you don't need to manually import it. However, this may cause issues if you are using `.tsx` with Preact since TS expects `h` to be explicitly imported for type inference. In that case, you can use the explicit factory config shown above which disables the auto `h` injection.\n\n\n\n# 606 - 63b0e3c resolveExt仅在添加了拓展名称才返回id + 新增resolveRequest，提供给requestToFile和resolveExt使用\n\n改动部分：\n\n- `src/node/resolver.ts`：实际上把`requestToFile`原有的逻辑封装为resolveRequest，新增的`resolveExt`走`requestToFile`拿到`filePath`再调用`resolveExt`拿到带有后缀的`filePath`完整路径。（详 **改动一**）\n- `src/node/server/serverPluginModuleRewrite.ts`：`resolveImport`路径处理中，步骤3添加拓展名改由直接调用`resolver.resolveExt`。（详 **改动二**）\n\n> PR那次的更改考虑的是包内走vite的逻辑，现在完全分离开了，包内走`resolveFrom`，即`resolve.sync`（这里的resolve是一个工具包，`\"resolve\": \"^1.17.0\"`）\n\n### 改动一 resolveExt走完整流程\n\n`resolver.resolveExt`等同于`resolveRequest`，如果没有添加拓展名称则不做任何处理。\n\n> 添加拓展名确实需要确定filePath，不然只是一个单纯在当前传递进的文件夹下寻找。\n\n```typescript\nexport function createResolver(\n  root: string,\n  resolvers: Resolver[] = [],\n  alias: Record<string, string> = {}\n): InternalResolver {\n  // 逻辑上，与旧的requestToFile没有任何改变  \n  function resolveRequest(\n    publicPath: string\n  ): {\n    filePath: string\n    ext: string | undefined\n  } {\n    let resolved: string | undefined\n    for (const r of resolvers) {\n      const filepath = r.requestToFile && r.requestToFile(publicPath, root)\n      if (filepath) {\n        resolved = filepath\n        break\n      }\n    }\n    if (!resolved) {\n      resolved = defaultRequestToFile(publicPath, root)\n    }\n    const ext = resolveExt(resolved)\n    return {\n      filePath: ext ? resolved + ext : resolved, // resolveExt在没有拓展为undefinded\n      ext\n    }\n  }\n\n  return {\n    requestToFile(publicPath) {\n      return resolveRequest(publicPath).filePath\n    },\n\n    resolveExt(publicPath) {\n      return resolveRequest(publicPath).ext\n    }\n  }\n}\n```\n\n### 改动二 添加后缀改由使用`resolver.resolveExt`\n\n```typescript\nexport const resolveImport = (\n  root: string,\n  importer: string,\n  id: string,\n  resolver: InternalResolver,\n  timestamp?: string\n): string => {\n  id = resolver.alias(id) || id\n  if (bareImportRE.test(id)) {\n  } else {\n    // 3. resolve extensions.\n    const ext = resolver.resolveExt(pathname)\n    if (ext) {\n      pathname += ext\n    }\n    return pathname + query\n  }\n}\n```\n\n\n\n# 607 - 2f071b3\n\nfeat: 改进 commonjs 依赖处理\n\n- Bump `rollup`, `@rollup/plugin-commonjs` and `@rollup/plugin-node-resolve` versions\n- Optimize CJS deps by default\n- Handle package with `jsnext` or `browser` entries\n- Add `rollupDedupe` option\n\nBREAKING CHANGE: The following config options have been removed:\n\n  - `rollupPluginCommonJSNamedExports`\n  - `optimizeDeps.commonJSWhitelist`\n\n  CommonJS deps are now optimized by default.\n\n改动部分：\n\n- `src/node/config.ts`：新增`rollupDedupe`选项，用于`@rollup/plugin-node-resolve`，[dedupe](https://github.com/rollup/plugins/tree/master/packages/node-resolve#dedupe)，去除`rollupPluginCommonJSNamedExports`。(详 **改动一**)\n- `src/node/depOptimizer.ts`：去除`commonJSWhitelist`，检查到`cjs`类型的包也将被列入优化中。（详 **改动二**）\n- `src/node/build/index.ts`：`@rollup/plugin-commonjs`升级，去除`knowNamedExports`相关逻辑。\n\n### 改动一\n\n### `dedupe`\n\nType: `Array[...String]`\nDefault: `[]`\n\n一个`Array`类型的模块名称，表示去根`node_modules`中处理模块，如果包是从依赖项导入的，则有助于防止多次捆绑同一个包。\n\n```\ndedupe: ['my-package', '@namespace/my-package'];\n```\n\n这将对裸导入进行重复数据删除，例如：\n\n```\nimport 'my-package';\nimport '@namespace/my-package';\n```\n\n它将对深导入进行重复数据删除，例如：\n\n```\nimport 'my-package/foo.js';\nimport '@namespace/my-package/bar.js';\n```\n\n### 改动二 cjs列入优化中\n\n类似`cjs`的都会被列入优化中（cjs的白名单被砍掉了，意味着所有的包都可以被依赖优化）\n\n```typescript\nif (!exports.length && !/export\\s+\\*\\s+from/.test(content)) {\n  debug(`optimizing ${id} (no exports, likely commonjs)`)\n  return true\n}\n```\n\n> 为什么现在可以，以前不可以？当然是rollup现在修复了BUG\n>\n> [feat(node-resolve): Export defaults by MichaelDeBoey · Pull Request #301 · rollup/plugins (github.com)](https://github.com/rollup/plugins/pull/301)\n\n\n\n# 608 - 482bd34 fix #209 如果没有config文件，要从命令行`--mode`加载\n\n![1](1.png)\n\n\n\n# 609 - 8e41db2 hashRE正则修改\n\n`#`在正则没有特殊含义，所以不需要`\\`。\n\n![2](2.png)\n\n\n\n# 610 - 801951e feat alias支持文件夹方式\n\n改动部分：\n\n- `src/node/config.ts`：更新使用alias的方式。（详 **改动一**）\n- `src/node/resolver.ts`：变量改名 + alias支持文件夹，本质也是`requestToFile` & `fileToRequest`做跳转（详 **改动二**）\n\n### 改动一 alias用例\n\n```typescript\n# vite.config.js\n\nmodule.exports = {\n    alias: {\n        'react': '@pika/react',\n        'react-dom': '@pika/react-dom',\n        // key需要‘/’开头并'/'结尾\n        '/@foo/': path.resolve(__dirname, 'some-special-dir')\n    }\n}\n```\n\n### 改动二 \n\n我看来有3个描述：\n\n1. id为用户import句子写的最原始形态\n2.  request为浏览器请求服务器即\n3. vite根据id改写得来，file为文件在磁盘中的路径\n\n`idToFileMap` -> `moduleIdToFileMap`\n\n`fileToRequestMap` -> `moduleFileToIdMap`\n\n> alias可以从config.resolvers配置，也可以从config.alias中配置。\n\n```typescript\nexport interface Resolver {\n    alias?: ((id: string) => string | undefined) | Record<string, string>\n}\n\nconst resolveAlias = (alias: Record<string, string>) => {\n  for (const key in alias) {\n    let target = alias[key]\n    // 符合/XXX/可以当作alias文件夹处理\n    if (key.startsWith('/') && key.endsWith('/') && path.isAbsolute(target)) {\n      // 检测是不是从root下alias的，不是的话不做任何处理\n      const fromRoot = path.join(root, target)\n      if (isDir(fromRoot)) {\n        target = fromRoot\n      } else if (!isDir(target)) {\n        continue\n      }\n       \n      // 本质也是requestToFile & fileToRequest做跳转\n      resolvers.push({\n        requestToFile(publicPath) {\n          if (publicPath.startsWith(key)) {\n            return path.join(target, publicPath.slice(key.length))\n          }\n        },\n        fileToRequest(filePath) {\n          if (filePath.startsWith(target)) {\n            return slash(key + path.relative(target, filePath))\n          }\n        }\n      })\n    } else {\n      // 当作字符转换处理  \n      literalAlias[key] = target\n    }\n  }\n}\n\n// alias可以从config.resolvers配置，也可以从config.alias中配置。\nresolvers.forEach((r) => {\n  if (r.alias && typeof r.alias === 'object') {\n    resolveAlias(r.alias)\n  }\n})\nresolveAlias(userAlias)\n\n// alias调用\nalias(id) {\n  let aliased: string | undefined = literalAlias[id]\n  if (aliased) {\n    return aliased\n  }\n  for (const r of resolvers) {\n    aliased =\n      r.alias && typeof r.alias === 'function' ? r.alias(id) : undefined\n    if (aliased) {\n      return aliased\n    }\n  }\n}\n```\n\n"
  },
  {
    "path": "61-70/commit-61-70.md",
    "content": "# 61 - 7eda441  readme\n\n新增build参数配置（但是未实现）\n\n- `vite build --root dir`:  在目标目录而不是当前工作目录中生成文件\n\n- `vite build --cdn`:  从CDN中引入```vue```，这样可以使构建速度更快，但总的来说，页面负载将更大，因为```VUE API```不会被```tree-shaking```。\n\n# 62 - 28a436a v0.5.1\n\nrelease v0.5.1\n\n# 63 - 093cc00 readme\n\n补充```npx vite```，```npx```会自动把```vite```下载在```npm```缓存中。（使用后会自动删除，所以不用担心无法拉取最新的```vite```的问题）\n\n>除了调用项目内部模块，```npx``` 还能避免全局安装的模块。比如，````create-react-app````这个模块是全局安装，```npx``` 可以运行它，而且不进行全局安装。\n>\n>> ```bash\n>> $ npx create-react-app my-react-app\n>> ```\n>\n>上面代码运行时，```npx``` 将`create-react-app`下载到一个临时目录，使用以后再删除。所以，以后再次执行上面的命令，会重新下载`create-react-app`。\n>\n>[原文](https://www.ruanyifeng.com/blog/2019/02/npx.html)\n\n# 64 - d43d779 修改readme的单词错误\n\n[fix#10](https://github.com/vitejs/vite/pull/10)，说起来也好笑... 混一个```vite```的```contributor```也太容易了吧。\n\n\n\n# 65 - f402c7b ```vite```服务添加```ip```提醒\n\n[feat #8](https://github.com/vitejs/vite/pull/8)，在使用```vite```开启服务的时候，我们可能不想使用```localhost```，想使用```ip```的方式。\n\n\n\n# 66 - 59992ea readme\n\n尤大觉得```vite```很快，就在```readme```文档大标题，加了一个⚡符号。\n\n\n\n# 67 - 6cef3fe fix ```style```的```hmr```问题\n\n在```commit-67```之前，```module```的改变，不会触发```hmr```，现在暂时触发```vue-reload```事件，快速修复问题为主，我们在做业务出现```bug```的时候，可以借鉴用简单的方法先解决问题，用注释去标记正确做法。\n\n```typescript\nif (\n      prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped) ||\n      // TODO for now we force the component to reload on <style module> change\n      // but this should be optimizable to replace the __cssMoudles object\n      // on script and only trigger a rerender.\n    \n    // 我们先强制触发vue-reload事件\n    // 实际上我们可以优化成打包出来的js组件去除__cssMoudles对象，并重新触发组件render\n    \n      prevStyles.some((s) => s.module != null) ||\n      nextStyles.some((s) => s.module != null)\n    ) {\n      notify({\n        type: 'vue-reload',\n        path: servedPath,\n        timestamp\n      })\n    }\n```\n\n\n\n# 68 - 81d3399 127.0.0.1修改为localhost\n\n在[fix#10](https://github.com/vitejs/vite/pull/10)中，优化```localhost```成为```ip```地址，但是我们可以把```127.0.0.1```输出的提示替换为```localhost```\n\n\n\n# 69 - 8c8879c readme英语语法错误\n\n[fix#12](https://github.com/vitejs/vite/pull/12)，英文语法错误。\n\n\n\n# 70 - 40f170c\n\n## 实现```vite build --cdn```\n\n判断是否设置了```--cdn```，传入```vite```的构建方法入口。\n\n在build方法执行后，设置```process.env.NODE_ENV = 'production'```\n\n## rollup配置```preserveEntrySignatures```为false\n\n[文档](https://rollupjs.org/guide/en/#preserveentrysignatures)\n\n```false```: 不会将入口模块的任何```export```添加到相应的块中，甚至不包含相应的代码，除非在包中的其他地方使用。但是，内部导出可以添加到条目块中。这是 Web 应用程序的推荐设置，其中条目块将放置在脚本标记中，因为它可能会减少块的数量和包大小。\n\n**Example**\nInput:\n\n```\n// main.js\nimport { shared } from './lib.js';\nexport const value = `value: ${shared}`;\nimport('./dynamic.js');\n\n// lib.js\nexport const shared = 'shared';\n\n// dynamic.js\nimport { shared } from './lib.js';\nconsole.log(shared);\n```\n\nOutput for `preserveEntrySignatures: false`\n\n```\n// main.js\nimport('./dynamic-39821cef.js');\n\n// dynamic-39821cef.js\nconst shared = 'shared';\n\nconsole.log(shared);\n```\n\n\n\n"
  },
  {
    "path": "611-620/611-620.md",
    "content": "# 611 - 71b1d0e changelog\n\n# [0.18.0](https://github.com/vuejs/vite/compare/v0.17.2...v0.18.0) (2020-05-28)\n\n### Bug Fixes\n\n- 添加拓展名称需要先获取文件路径 ([63b0e3c](https://github.com/vuejs/vite/commit/63b0e3cca2975a180e8372882c4e8d9b513fc7cf))\n- 如果没有`config`文件，要从命令行`--mode`加载 ([482bd34](https://github.com/vuejs/vite/commit/482bd3482687697d7092c0ae18fb699228a4cc5d)), closes [#290](https://github.com/vuejs/vite/issues/290)\n\n### Features\n\n- `cjs`可以纳入依赖优化中 ([2f071b3](https://github.com/vuejs/vite/commit/2f071b386175737f7e1146ba8154944ca2b7390a))\n- `alias`可以关联文件路径 ([801951e](https://github.com/vuejs/vite/commit/801951e28a92aaf7437647094081825ec308e645))\n\n### BREAKING CHANGES\n\n- 一下config选项已经被移除:\n\n  - `rollupPluginCommonJSNamedExports`\n  - `optimizeDeps.commonJSWhitelist`\n\n  `cjs`模块现在都可以被依赖优化了。\n\n\n\n# 612 - 00a385a v0.18.0\n\nrelease v0.18.0\n\n\n\n# 613 - cddbebc #297 `KNOWN_IGNORE_LIST`添加`vite`\n\n即`vite`不纳入依赖优化。\n\n\n\n# 614 - e35cf46 docs代码贡献规范\n\n`pr`提交可以显示提示。\n\n![1](1.png)\n\n\n\n# 615 - 28d9714 fix #294 修复`package.json`  `browser`字段\n\n之前取得模块入口是直接取`browser`字段，但是`browser`也会有`object`的情况，这情况没有考虑。\n\n> 实际就是在取得main入口后，把它作为key去browser取得的值作为入口\n\nhttps://github.com/defunctzombie/package-browser-field-spec\n\n改动部分：\n\n- `src/node/resolver.ts`：`mapWithBrowserField`用来专门处理当`browser`字段为对象的时候，包的入口。\n\n```typescript\n// resolve browser field in package.json\n// https://github.com/defunctzombie/package-browser-field-spec\nconst browserField = pkg.browser\nif (typeof browserField === 'string') {\n  entryPoint = browserField\n} else if (\n  entryPoint &&\n  typeof browserField === 'object' &&\n  browserField !== null\n) {\n  entryPoint = mapWithBrowserField(entryPoint, browserField)\n}\n\n/**\n * given a relative path in pkg dir,\n * return a relative path in pkg dir,\n * mapped with the \"map\" object\n */\nfunction mapWithBrowserField(\n  relativePathInPkgDir: string,\n  map: Record<string, string>\n) {\n  const normalized = path.normalize(relativePathInPkgDir)\n  const foundEntry = Object.entries(map).find(([from]) => {\n    return path.normalize(from) === normalized\n  })\n  if (!foundEntry) {\n    return normalized\n  }\n  const [, to] = foundEntry\n  return path.normalize(to)\n}\n```\n\n> [path.normalize(path) | Node.js API 文档 (nodejs.cn)](http://nodejs.cn/api/path/path_normalize_path.html)\n>\n> `path.normalize()` 方法规范化给定的 `path`，解析 `'..'` 和 `'.'` 片段。\n>\n> ```js\n> path.normalize('/foo/bar//baz/asdf/quux/..');\n> // 返回: '/foo/bar/baz/asdf'\n> ```\n\n\n\n# 616 - 8144044 修复windows下的browser入口\n\n617中没有考虑到windows，在vite中实际是统一使用`/`的，不使用winodws路径，所以`path.normalize`被替换为`path.posix.normalize`。\n\n\n\n# 617 - d623437 支持在包中的`scss`引入\n\npostcss包含`node_modules`即可。\n\n"
  },
  {
    "path": "71-80/commit-71-80.md",
    "content": "# 71 - 1147a79 create-vite-app模板更新\n\n新增```create-vite-app```文件夹，可以通过命令`npx create-vite-app <project-name>`来创建。\n\n![image-20210730051910514](./temp.png)\n\n\n\n# 72 - 031bf4c `vite`为什么如此快？\n\n## Getting Started\n\n```bash\n$ npx create-vite-app <project-name>\n$ cd <project-name>\n$ npm install\n$ npm run dev\n```\n\nIf using Yarn:\n\n```bash\n$ yarn create vite-app <project-name>\n$ cd <project-name>\n$ yarn\n$ yarn dev\n```\n\n## 这与基于捆绑程序的设置有何不同?\n\n主要区别在于，对于`vite`，在**开发过程中**没有捆绑。源代码中的**ES导入语法**直接提供给浏览器，浏览器通过`<script module>`支持和解析它们，为每次`import`发出HTTP请求。dev服务器拦截请求，并在必要时执行代码转换。例如，对`*.vue`文件的`import`，在发送给浏览器之前立即编译。\n\n这种方法有几个优点:\n\n- 由于没有捆绑工作要做，服务器冷启动极快\n- 代码是按需编译的，所以只编译当前屏幕上实际导入的代码。你不必等到整个应用程序被捆绑后才能开始开发。这对于具有数十个屏幕的应用程序来说可能是一个巨大的差异\n- 热模块更换 (HMR) 性能与模块总数分离。无论你的应用程序有多大，HMR 都始终快速\n\n整页重新加载可能比基于捆绑器的设置稍慢，因为原生 ES `import`会导致一系列**深度`import`的网络请求**。然而，由于这是本地开发，与实际编译时间相比，差异应该是微不足道的。 （页面重新加载没有编译成本，因为已经编译的文件缓存在内存中。）\n\n最后，因为编译在 `Node`环境 中完成，**它在技术上可以支持任何捆绑器进行代码转换**。事实上，`vite` 提供了一个 `vite build` 命令来做到这一点，因此应用程序在生产中不会受到**深度`import`的网络请求**的影响。\n\n`vite` 在这个阶段是高度实验性的，不适合生产使用，但我们希望有一天能做到。\n\n\n\n# 73 - de131ac v0.5.2\n\nrelease v0.5.2\n\n\n\n# 74 - de25435\n\n```package.json```修正入口路径。\n\n\n\n# 75 - cdcb930 修复template与style同时hmr的BUG\n\n修复`<template>`与`<style>`同时变动，触发hmr，只更新`<template>`的BUG。\n\n顺带整理代码。\n\n引起该`bug`的原因是`hmr`优先触发`vue-render`事件，触发后跳过`<style>`的`hmr`的处理了。\n\n\n\n# 76 - 519b9dd v0.5.3\n\n发布`v0.5.3`\n\n\n\n# 77 - fd1ddaa 重构暴露路径处理器\n\n`publicPath`: 浏览器请求资源路径。\n\n`filePath`: 文件绝对路径。\n\n> 在配置中暴露了一个`resolver`方法，尤大打算让处理文件不再写死。使用者可以修改`publicPath`与`filePath`\n\n(未看见其使用，后续`commit`会继续分析)\n\n\n\n# 78 - ecc7219 添加http协商缓存\n\n```typescript\nexport async function cachedRead(ctx: Context, file: string) {\n  // 指示最后一次修改此文件的时间戳，以 POSIX Epoch 以来的毫秒数表示。\n  const lastModified = (await fs.stat(file)).mtimeMs\n  \n  const cached = moduleReadCache.get(file)\n  ctx.set('Cache-Control', 'no-cache')\n  ctx.type = path.basename(file)\n  if (cached && cached.lastModified === lastModified) {\n    ctx.etag = cached.etag\n    ctx.lastModified = new Date(cached.lastModified)\n    ctx.status = 304\n    return cached.content\n  }\n  const content = await fs.readFile(file, 'utf-8')\n  const etag = getETag(content)\n  moduleReadCache.set(file, {\n    content,\n    etag,\n    lastModified\n  })\n  ctx.etag = etag\n  ctx.lastModified = new Date(lastModified)\n  ctx.body = content\n  ctx.status = 200\n}\n```\n\n添加缓存。\n\n## http中etag和lastModified哪个优先级高\n\n当ETag和Last-Modified同时存在时，服务器先会检查ETag，然后再检查Last-Modified，最终决定返回304还是200。\n\n## 那为什么同时设置两个呢\n\nhttps://segmentfault.com/q/1010000004200644。两者是and的关系。\n\nETag 比较的是响应内容的特征值，而Last-Modified 比较的是响应内容的修改时间。这两个是相辅相成的，并不是说有了ETag就不该有Last-Modified，有Last-Modified就不该有ETag。同时传入服务器时，服务器可以根据自己的缓存机制的需要，选择ETag或者是Last-Modified来做缓存判断的依据，甚至可以两个同时参考。\n\n> `mtimeMs`的使用，可以查看commit- 82&83的解析，主要是后续重构把所有用到fs.read的地方都使用缓存，所以需要根据修改时间来判断是否使用缓存\n\n\n\n# 79 - 706c597 fix测试的BUG\n\n测试启动的时候，会判断是否子线程输出`running`。现在要同步修改成`Running`\n\n```typescript\ntest('test', async () => {\n  server = execa(path.resolve(__dirname, '../bin/vite.js'), {\n    cwd: tempDir\n  })\n  await new Promise((resolve) => {\n    server.stdout.on('data', (data) => {\n      if (data.toString().match('Running')) {\n        resolve()\n      }\n    })\n  })\n })\n```\n\n\n\n# 80 - 78ae1b7 fix处理模块的BUG\n\n[#15](https://github.com/vitejs/vite/pull/15)\n\n在vite中，所有源文件的`import Lodash from 'lodash'`模块类的都会被改写成`import Lodash from '@modules/lodash'`。\n\n然后在发送该模块的时候，会使用正则去除`@modules`，得到`lodash`。\n\n```typescript\nlet module = id = 'lodash'\nconst deepIndex = id.indexOf('/')\n\nif (deepIndex > 0) {\n    module = id.splice(0, deepIndex)\n}\n\n// ...\n```\n\n随后根据正则，获取到模块名称`module`。\n\n但是如果引入的是：\n`import { parse } from '@vue/compiler-dom`，我们得到的模块名称为`@vue`，那么就引起错误了。\n\n原本加入`indexOf('/')`这些代码的目的是防止`lodash/index.js`的发生，实际上我们可以用`path.extname`判断是否有拓展名来过滤掉`/index.js`。\n\n## 什么是scope module\n\n所有npm模块都有name，有的模块的name还有scope。scope的命名规则和name差不多，同样不能有url非法字符或者下划线点符号开头。scope在模块name中使用时，以@开头，后边跟一个/ 。package.json中，name的写法如下：\n\n> @somescope/somepackagename\n\nscope是一种把相关的模块组织到一起的一种方式，也会在某些地方影响npm对模块的处理。\n\n带有scope的模块安装在一个子目录中，如果正常的模块安装在node_modules/packagename目录下，那么带有scope的模块安装在node_modules/@myorg/packagename目录下，@myorg就是scope前面加上了@符号，一个scope中可以包含很多个模块。\n\n安装一个带有scope的模块：\n\n> npm install @myorg/mypackage\n\n在package.json中写明一个依赖:\n\n> \"dependencies\": {\n> \"@myorg/mypackage\": \"^1.3.0\"\n> }\n\n如果@符号被省略，那么npm会尝试从github中安装模块，在npm install命令的文档中有说明 https://docs.npmjs.com/cli/install\n\n"
  },
  {
    "path": "81-90/commit-81-90.md",
    "content": "# commit-81 暴露watchAPI\n\n```typescript\nexport type ViteWatcher = FSWatcher & {\n  handleVueReload: (file: string, timestamp: number, content?: string) => void\n  handleJSReload: (file: string, timestamp: number) => void\n}\n  \ninterface PluginContext {\n  root: string\n  app: Koa\n  server: Server\n  watcher: ViteWatcher\n  resolver: InternalResolver\n}\n```\n\n```typescript\n# serverPluginHmr\nwatcher.handleVueReload = handleVueReload\nwatcher.handleJSReload = handleJSReload\n```\n\n`vue-plugins`上下文新增`watcher`。\n\n也就是说plugins现在可以操控`vue`或者`js`文件的`hmr`了。\n\n# commit-82&commit-83\n\n代码整理，把所有读取文件的地方都使用`cacheRead`方法来读取。 \n\n此处还有`ctx.vue`的相关代码(判断是否是vue文件)，但是还没有看到定义，只有其使用。\n\n## 如果把所有文件都缓存了起来，hmr岂不是无效？\n\n> 文件缓存的时候，会读取`(await fs.stat(file)).mtimeMs`修改时间，下次文件改变，将对比`mtimeMs`来决定是否使用缓存\n\n# commit-84 为vue文件添加http缓存\n\n**`If-None-Match`** 是一个条件式请求首部。对于 GET[`GET`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/GET) 和 [`HEAD`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/HEAD) 请求方法来说，当且仅当服务器上没有任何资源的 [`ETag`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/ETag) 属性值与这个首部中列出的相匹配的时候，服务器端会才返回所请求的资源，响应码为 [`200`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/200)。\n\n当你**第一次**发起HTTP请求时，**服务器**会返回一个**Etag**。\n\n并在你**第二次**发起**同一个请求**时，客户端会**同时**发送一个**If-None-Match**，而它的值就是**Etag**的值（此处由发起请求的客户端来设置）。\n\n然后，**服务器会比**对这个客服端发送过来的Etag**是否与服务器的相同**，如果**相同**，就将**If-None-Match**的值设为**false**，返回状态为**304，****客户端**继续**使用本地缓存**，不解析服务器返回的数据。\n\n请求`vue组件`，判断request请求的`If-None-Match`是否与文件`etag`一致，一致则返回`ctx.status = 200`\n\n## 之前不是已经设置过了缓存了吗？\n\n首先说明一下，封装的`cachedRead`只对文件来说适用，对于`vue组件`来说，是需要编译的。\n\n为了解决这个问题，尤大不再使用`cacheRead`对于`vue`文件，而是在编译了三大标签（`<style>` `<template>` `<script>`）后使用`etagCacheCheck`：\n\n```typescript\nconst etagCacheCheck = (ctx: Context) => {\n  ctx.etag = getEtag(ctx.body)\n  if (ctx.etag !== ctx.get('If-None-Match')) {\n    ctx.status = 200\n  }\n    // 好奇 为什么不else后返回304? 不然一样无效的 可能是因为想调试方便吧，浏览器点击就弹出源码了，不用查看sources\n}\n```\n\n# commit-85 添加缓存\n\n为访问`web_modules`使用`cacheRead`。\n\n为访问`node_modules`的文件路径结果添加上缓存，不使用`cacheRead`的原因是跳转:\n\n```typescript\n# serverPluginModules\n\n// resolve from web_modules\n    try {\n      const webModulePath = await resolveWebModule(root, id)\n      if (webModulePath) {\n        idToFileMap.set(id, webModulePath)\n        await cachedRead(ctx, webModulePath)\n        debugModuleResolution(\n          `web_modules: ${id} -> ${getDebugPath(webModulePath)}`\n        )\n        return\n      }\n    } catch (e) {\n      console.error(\n        chalk.red(`[vite] Error while resolving web_modules with id \"${id}\":`)\n      )\n      console.error(e)\n      ctx.status = 404\n    }\n\n    // resolve from node_modules\n    try {\n      let pkgPath\n      try {\n        pkgPath = resolve(root, `${id}/package.json`)\n      } catch (e) {}\n      if (pkgPath) {\n        const pkg = require(pkgPath)\n        const entryPoint = pkg.module || pkg.main || 'index.js'\n        debugModuleResolution(`node_modules entry: ${id} -> ${entryPoint}`)\n        idToEntryMap.set(id, entryPoint)\n        return ctx.redirect(path.join(ctx.path, entryPoint))\n      }\n      // in case of deep imports like 'foo/dist/bar.js'\n      const modulePath = resolve(root, id)\n      idToFileMap.set(id, modulePath)\n      debugModuleResolution(\n        `node_modules import: ${id} -> ${getDebugPath(modulePath)}`\n      )\n      await cachedRead(ctx, modulePath)\n    } catch (e) {\n      console.error(\n        chalk.red(`[vite] Error while resolving node_modules with id \"${id}\":`)\n      )\n      console.error(e)\n      ctx.status = 404\n    }\n```\n\n#### 可以利用ctx.redirt跳转\n\n可以使用`return ctx.redirect(path.join(ctx.path, cachedEntry))`的方法，更改请求`path`路径（对于服务端来说，对于一整个`http`是没有影响的）。\n\n# commit-86\n\n为readCache方法设置`If-None-Match`。\n\n```typescript\n# node/util\nfunction readCache() {\n    // ... \n    if (ctx.get('If-None-Match') === ctx.tag) {\n        ctx.status = 304\n    }\n    // ...\n}\n\n```\n\n# commit-87 修复windows hmr问题\n\n新增slash包。\n\n## slash包\n\n`resolver.ts`中的`defaultFileToPublic`（转换文件路径到浏览器请求路径）方法，使用`slash`统一转换成`/`\n\n```typescript\nimport path from 'path';\nimport slash from 'slash';\n\nconst string = path.join('foo', 'bar');\n// Unix    => foo/bar\n// Windows => foo\\\\bar\n\nslash(string);\n// Unix    => foo/bar\n// Windows => foo/bar\n```\n\n# commit-88 v0.6.0\n\nrelease v0.6.0\n\n# commit-89 介绍变动v0.6.0\n\n### Bug Fixes\n\n- 为windows修复hmr的问题（[commit-53](https://github.com/Kingbultsea/vite-analysis/blob/master/commit-51-60/commit-51-60.md#handlejsreload)）\n- 如果etags匹配上了则返回304 ([e0c7354](https://github.com/vuejs/vite/commit/e0c73543a6c792046f9d7e9a0a481f567f4e21ec))\n- 修复` scoped packages`和引入的相对路径问题 ([#15](https://github.com/vuejs/vite/issues/15)) ([78ae1b7](https://github.com/vuejs/vite/commit/78ae1b745bc5cf269b6ccfc12b129b404f0e9026))\n\n### Features\n\n- 为vue组件请求相关，添加http缓存 ([ecc7219](https://github.com/vuejs/vite/commit/ecc7219786e363988976f15d9223565a34a673eb))\n\n# commit-90\n\n我觉得这个commit没有任何意义，因为304已经`return`代码了。\n\n```typescript\nif (ctx.status === 304) {\n      return\n}\n\n// ...\nif (ctx.status === 304 && rewriteCache.has(ctx.url)) {\n        debugImportRewrite(`${ctx.url}: serving from cache`)\n        ctx.body = rewriteCache.get(ctx.url)\n}\n```\n"
  },
  {
    "path": "91-100/commit-91-100.md",
    "content": "# 91 - 43fe56f 修改字符\n\n`util.ts`中的`readCache`方法里面写错`etag`字母（原tag）。\n\n\n\n# 92 - 875c0c6 缓存304\n\n我在commit-84提过，为什么在`serverPluginVue.ts`中缓存不设置304，现在补充上。\n\n\n\n# 93 - 02b68d5 代码整理\n\n整理`commit-92`的代码。\n\n```typescript\n// old\nif (ctx.etag !== ctx.get('If-None-Match')) {\n    ctx.status = 200\n} else {\n    ctx.status = 304\n}\n\n// new\nctx.status = ctx.etag === ctx.get('If-None-Match') ? 304 : 200\n```\n\n\n\n# 94 - 488fec3 调整LRU值(降低)\n\n`rewriteCache`(涉及`index.html`与所有被改写`import`语句的文件)大小从`65535`改小成`1024`。\n\n（话说我去看`lru-cache`的文档，也不知道这个max对应的单位是多少...，如果有知道的童鞋请告诉我）\n\n\n\n# 95 - aceb5f7 设置node运行版本\n\n指定项目运行node版本的范围。\n\n> *engines*属性仅起到一个说明的*作用*，当用户版本不符合指定值时也不影响依赖的安装。\n\n```json\n\"engines\": {\n    \"node\": \">=10.0.0\"\n}\n```\n\n\n\n# 96 - c3d87db v0.6.1\n\nrelease v0.6.1\n\n\n\n# 97 - df07231 介绍v0.6.1变动\n\n### Bug Fixes\n\n- 改写`import语句`的功能，应该在缓存304的时候不触发 ([c3a7a96](https://github.com/vuejs/vite/commit/c3a7a967ee9048ca6fc2642b3494b0e60978bf24))\n- tag -> etag ([43fe56f](https://github.com/vuejs/vite/commit/43fe56f61b3f5cd8fc51d33916d79e154042bc8c))\n\n我真觉得那个`import语句`的304没有任何意义（详情看commit-90）。\n\n# 98 - 5794291 优化\n\n对于已经被处理过的请求，不再经过`serverPluginServe`。\n\n```typescript\napp.use((ctx, next) => {\n    if (ctx.body || ctx.status !== 404) {\n      return\n    }\n    return next()\n})\n\n// 使用到的koa插件...\n// https://www.npmjs.com/package/koa-etag\napp.use(require('koa-conditional-get')()) // 实现基于etag的缓存\napp.use(require('koa-etag')()) // 配合koa-conditional-get使用\napp.use(require('koa-static')(root)) // 静态文件处理\n```\n\n> serverPluginServe用于处理一些静态文件请求与一些header服务\n\n\n\n# 99 - d00523f 自动添加import文件后缀\n\n那个没有用的304已经被去除了，正如`commit-90`所说的一样。\n\n## 对于没有添加尾缀的import默认指向`js`文件\n\n重写`import`语句的时候，如果没有添加文件后缀则：\n\n```typescript\nif (!/\\.\\w+/.test(pathname)) {\n    pathname += '.js'\n}\n```\n\n\n\n# 100 - 8965b65 修改resolver的名称\n\n曾经`public`为浏览器请求使用，现在把`public`名称更改为`request`更符合语义了。\n\n\n\n"
  },
  {
    "path": "readme.md",
    "content": "# 1 - 820c2cf 根基\n\n### npm bin的知识点\n\n我们写包的时候，可以在package.json中添加bin字段。\n\n```json\n{\n  \"bin\": {\n    \"vds\": \"bin/vds.js\"\n  }\n}\n\n```\n\n当用户安装我们写的包，用户可以使用vds来执行 ```bin/vds.js```\n\nnpm为我们在```./bin```目录下面放了一个叫做```vds```的软连接，所以在目录下面执行命令行```vds```，就会执行```bin/vds.js```文件\n\n### Upgrade-Insecure-Requests请求头\n\nHTTP ```Upgrade-Insecure-Requests``` 请求头向服务器发送一个客户端对HTTPS加密和认证响应良好，并且可以成功处理的信号，可以请求所属网站所有的HTTPS资源。\n在https页面中，如果调用了http资源，那么浏览器就会抛出一些错误。为了改变成这一状况，chrome(谷歌浏览器)会在http请求中加入 ```Upgrade-Insecure-Requests: 1``` ，服务器收到请求后会返回 ```Content-Security-Policy: upgrade-insecure-requests``` 头，告诉浏览器，可以把所属本站的所有 http 连接升级为 https 连接。\n\n### 是如何监听文件变动的\n\n利用工具```chokidar```，文件改动保存，即可触发（其实按下```ctrl + s```就已经触发了）\n\n```javascript\nconst chokidar = require('chokidar')\nconst fileWatcher = chokidar.watch(process.cwd(), {\n    ignored: [/node_modules/]\n  })\n\nfileWatcher.on('change', (file) => {\n    if (file.endsWith('.vue')) {\n        // do something\n    }\n})\n```\n\n### 流程\n\n1. 浏览器请求文件，服务端发送经过```@vue/compiler-sfc```处理，转换成Vue版本的AST。\n\n2. 服务器把AST整理成```js```，其中把```template```转换成```render```函数\n\n   ```javascript\n   // http://localhost:3000/file-vue/test.vue\n   \n       const script = {\n         name: \"123\",\n         data() {\n           return {\n             a: 123\n           }\n         }\n       }\n   \n   export default script\n   import { render } from \"/file-vue/test.vue?type=template\"\n   script.render = render\n   script.__hmrId = \"/file-vue/test.vue\"\n   ```\n\n### 准备工作\n\n1. hmrProxy.js文件：\n   目的是给浏览器运行```ws```，与服务器进行双向交流，使用```esm```的```import```，请求本地```vue```文件，进行上面说的流程。\n\n   比如，文件变动（变动也分类型，比如现在是对比出旧文件和新文件的AST语法树中的```script.content```内容字符串有变动，事件名称```reload```，```template```的改动事件名称为```rerender```），通知前端事件  ```reload```，使用```import```异步引入```parse```后的内容（如上面的代码），再重新运行（这块还没写，预备位置）。\n\n2.对于```vue```文件的```style```还未处理，停留在```vue```的```AST```上。\n\n3.准备前端事件```update-style```与```full-reload```\n\n```javascript\n// 这个文件运行在客户端上\n\nconst socket = new WebSocket(`ws://${location.host}`)\n\n// 监听服务器发送的事件\nsocket.addEventListener('message', ({ data }) => {\n  const { type, path, index } = JSON.parse(data)\n  switch (type) {\n    case 'connected':\n      console.log(`[vds] connected.`)\n      break\n    case 'reload':\n      import(`${path}?t=${Date.now()}`).then(m => {\n        __VUE_HMR_RUNTIME__.reload(path, m.default) // m为AST转换成的js文件内容\n        console.log(`[vds][hmr] ${path} reloaded.`)\n      })\n      break\n    case 'rerender':\n      import(`${path}?type=template&t=${Date.now()}`).then(m => {\n        __VUE_HMR_RUNTIME__.rerender(path, m.render)\n        console.log(`[vds][hmr] ${path} template updated.`)\n      })\n      break\n    case 'update-style':\n      import(`${path}?type=style&index=${index}&t=${Date.now()}`).then(m => {\n        // TODO style hmr  留坑\n      })\n      break\n    case 'full-reload':\n      location.reload() // 刷新浏览器\n  }\n})\n\n// ping server 如果服务器ws连接中断，进行每秒的重新链接\nsocket.addEventListener('close', () => {\n  console.log(`[vds] server connection lost. polling for restart...`)\n  setInterval(() => {\n    new WebSocket(`ws://${location.host}`).addEventListener('open', () => {\n      location.reload() // 这里有BUG 需要把setInterval给删除\n    })\n  }, 1000)\n})\n```\n\n\n\n# 2 - 4a04d81 \n\n### server.js 新增serve-handler\n\n利用这个包，改写除了__hmrClient和.vue后缀的文件的路径，比如访问```localhost:3000/a```会自动修正为```localhost:3030/index.html```\n\n### parseSFC.js 处理SFC文件时，增加可以配置的缓存\n\n```javascript\nif (saveCache) { // 新增是否保存缓存\n    cache.set(filename, descriptor)\n}\n```\n\n### server.js 修复BUG\n\n```javascript\n// 提升 以下这段代码上一个层级\nconst fileWatcher = chokidar.watch(process.cwd(), {\n    ignored: [/node_modules/]\n})\n```\n\n\n\n# 3 - 33488fe 新增+优化\n\n### moduleRewriter.js\n\n使用```magic-string```重写AST语法树输出script中的```export script```\n\n### server.js新增访问__modules与.js文件\n\n```javascript\nif (pathname.startsWith('/__modules/')) {\n    return moduleMiddleware(pathname.replace('/__modules/', ''), res)\n  } else if (pathname.endsWith('.vue')) {\n    return vue(req, res)  // 发送vue文件 需要vue中间件 （@vue/compiler-sfc）\n  } else if (pathname.endsWith('.js')) { // 发送JS\n    const filename = path.join(process.cwd(), pathname.slice(1))\n    if (fs.existsSync(filename)) {\n      const content = rewrite(fs.readFileSync(filename, 'utf-8'))\n      return sendJS(res, content)\n    }\n }\n```\n\n### moduleMiddleware.js\n\n当访问localhost:3000/__modules/abc，通过```require.resolve('abc')```拿到绝对路径，把文件内容放进```response```响应体中。关于```require.resolve```：\n\n[require.resolve是如何寻找文件的](https://juejin.cn/post/6844904055806885895)\n\n### 处理好的文件中的变量增加下划线标识\n\n```___script``` ```__render```\n\n引入```hmrClient.js```\n\n```javascript\nimport \"/__hmrClient\" // 新增，这是一个客户端client ws 文件，在commit-1中就存在\n// __hmrClient 会自动访问 hmrClient.js\n\n    let __script; export default (__script = {\n      name: \"123\",\n      data() {\n        return {\n          a: 123\n        }\n      }\n    })\n\nimport { render as __render } from \"/file-vue/test.vue?type=template\"\n__script.render = __render\n__script.__hmrId = \"/file-vue/test.vue\"\n```\n\n\n\n# 4 - bfb4b91 重构\n\n### parseSFC.js阅读文件，修改成promise的方法\n\n曾经是同步读取文件，会阻塞线程，现在改用promises，去除需要等待IO处理的时间。\n\n```javascript\n// ./lib/hmrWatcher.js\nconst [descriptor, prevDescriptor] = await parseSFC(file)\n```\n\n### moduleMiddleware.js\n\n1. 处理```__modules```的```moduleMiddleware.js```曾经使用```require.resolve('abc')```确定路径，现在改为使用```resolve-cwd```模块。不同之处在于```resolve-cwd```会在命令行当前的路径**开始**寻找，其他行为与```require.resolve```一致。\n   ```http://localhost:3000/__modules/vue``` -->```命令行当前目录/node_modules/vue/index.js```\n   如果是用```require.resolve```，且当前工作目录下没有vue模块（当然更没有当前vue包，详情看文章），则会在当前命令行目录的上级路径查找模块，直到没有为止。\n\n   ```javascript\n   require.resolve('diff')\n   // 寻找的路径[\"Users/当前工作目录/node_modules\", \"/Users/node_modules\", \"/node_modules\"]\n   ```\n\n   可以看到是往上寻找的。\n\n```javascript\nconst resolve = require('resolve-cwd')\nmodulePath = resolve(id)\n```\n\n2. 曾经使用```sendJS```改为```sendJSStream```，在发送文件的时候，新增```Transfer-Encoding: chunked```response header。\n\n   > 数据以一系列分块的形式进行发送。 [`Content-Length`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Length) 首部在这种情况下不被发送。在每一个分块的开头需要添加当前分块的长度，以十六进制的形式表示，后面紧跟着 '`\\r\\n`' ，之后是分块本身，后面也是'`\\r\\n`' 。终止块是一个常规的分块，不同之处在于其长度为0。终止块后面是一个挂载（trailer），由一系列（或者为空）的实体消息首部构成。\n\n```javascript\nfunction send(res, source, mime) {\n  res.setHeader('Content-Type', mime)\n  res.end(source)\n}\n\nfunction sendJS(res, source) {\n  send(res, source, 'application/javascript')\n}\n\nfunction sendJSStream(res, file) {\n  res.setHeader('Content-Type', 'application/javascript')\n  const stream = fs.createReadStream(file)\n  stream.on('open', () => {\n    stream.pipe(res)\n  })\n  stream.on('error', (err) => {\n    res.end(err)\n  })\n}\n```\n\n### server.js expose createAPI\n\n封装```server```。\n\n```javascript\n#!/usr/bin/env node\nconst { createServer } = require('../lib/server')\n\n// TODO pass cli args\ncreateServer()\n// 封装后 就不直接像之前 require('')文件直接使用了，而且可以配置port，默认3000\n```\n\n### package.json添加main字段，指向./lib/server.js\n\n```javascript\n  \"main\": \"lib/server.js\"\n```\n\n\n\n# 5 - 91d76bf 使用TS重构内容\n\n### lint-staged\n\n```json\n{\n    \"gitHooks\": { // yorkie 不知道的 去搜索下git仓库 yorkie\n      \"pre-commit\": \"lint-staged\"\n     },\n     \"lint-staged\": {\n       \"*.js\": [\n         \"prettier --write\",\n         \"git add\"\n       ],\n       \"*.ts\": [\n         \"prettier --parser=typescript --write\",\n         \"git add\"\n       ]\n     }\n}\n```\n\n假如你改动了```index.ts```文件，并且git add 把文件丢进暂存区。\n\n```typescript\n// ./src/server/index.ts\nexport { createServer, ServerConfig } from \"./server\"\n+ let a = 'a'\n```\n\n改动.prettierrc，把单括号模式关闭（这个改动加不加都没所谓，执行的是你现在本身的文件内容）\n\n```\nsemi: false\n- singleQuote: true\n+ singleQuote: false\nprintWidth: 80\ntrailingComma: none\n```\n\n```git commit```，识别到是commit行为，通过钩子执行```lint-staged```命令，改动的是js文件，执行```prettier --parser=typescript --write```，你会发现你的代码从单括号变成双括号了，并且提交到本地仓库的也是双括号。\n\n```javascript\n// ./src/server/index.ts\nexport { createServer, ServerConfig } from \"./server\"\nlet a = \"a\"\n```\n\n### TS相关\n\n客户端运行的代码在```src/client```，服务端运行的代码在```src/server```。\n\n关于从js ---> ts的重构文件源与目标\n\n- ```./lib/hmrClient.js```  ---> ```./src/client/client.ts```\n- ```./lib/hmrWatcher.js```  ---> ```./src/server/hmrWatcher.ts```\n- ```./bin/vds.js```  ---> ```./src/server/index.ts``` // \"main\": \"dist/server/index.js\"\n- ```./lib/moduleMiddleware.js```  ---> ```./src/server/moduleMiddleware.ts```\n- ```./lib/moduleRewriter.js```  ---> ```./src/server/moduleRewriter.ts```\n- ```./lib/parseSFC.js```  ---> ```./src/server/parseSFC.ts```\n- ```./lib/server.js```  ---> ```./src/server/server.ts```\n- ```./lib/util.js```  ---> ```./src/server/util.ts```\n- ```./lib/vueMiddleware.js```  ---> ```./src/server/vueMiddleware.ts```####\n\n#### tsconfig.base.json\n\nserver与client公用（使用extends: \"../../tsconfig.base.json\"）。\n\n```json\n{\n  \"compilerOptions\": {\n    \"sourceMap\": false,\n    \"target\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"declaration\": true,\n    \"allowJs\": false,\n    \"allowSyntheticDefaultImports\": true,\n    \"noUnusedLocals\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": true,\n    \"removeComments\": false,\n    \"lib\": [\n      \"esnext\",\n      \"DOM\"\n    ]\n  }\n}\n\n```\n\n\n\n#### ./src/client/tsconfig.json\n\n```json\n{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"outDir\": \"../../dist/client\", // 输出位置\n    \"module\": \"esnext\", // 生成输出支持esnext的代码\n    \"lib\": [\"ESNext\", \"DOM\"] // 需要注入的库 DOM 与 esnext(就是语法啦)\n  },\n  \"include\": [\"./\"]\n}\n```\n\n#### ./src/client/tsconfig,json\n\n```json\n{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"outDir\": \"../../dist/server\",\n    \"module\": \"commonjs\", // 生成cjs的语法 可以说是es5了\n    \"lib\": [\"ESNext\"]\n  },\n  \"include\": [\"./\"]\n}\n\n```\n\n与```client```的区别是，```server```不需要使用```esm```/```node```，客户端必须使用```esm```，因为那是灵魂。\n\n### 命令\n\n```jso\n\"scripts\": {\n    \"dev\": \"run-p dev-client dev-server\",\n    \"dev-client\": \"tsc -w --p src/client\",\n    \"dev-server\": \"tsc -w --p src/server\",\n    \"build\": \"tsc -p src/client && tsc -p src/server\",\n    \"lint\": \"prettier --write --parser typescript \\\"src/**/*.ts\\\"\",\n    \"prepublishOnly\": \"tsc\"\n  }\n```\n\n- **dev**：run-p为npm-run-all包的命令（前面有介绍过package.json中的main字段），利用这个工具，可以同时运行两个命令，并非顺序运行。\n- **dev-client**: typescript的编译命令tsc，--w 监听文件变动，使得文件自动编译。--p 后接文件路径。\n- **dev-server**: 同上。\n- **build**: server and client编译(采用-p src/client 的tsconfig / -p src/server 的 tsconfig)。\n- **lint**: 格式```src/**/*.ts```路径下的ts文件, ```--parser```表示需要格式的文件是typescript文件。\n- **prepublishOnly**: 无效的命令... 不知道为什么而准备\n\n### 更新cli文件\n\n```javascript\n- const { createServer } = require('../lib/server')\n+ const { createServer } = reuqire('../dist/server')\n```\n\n\n\n# 6 - e718bd5 添加测试\n\n### server/server.ts\n\n给promise，返回ws（如果已连接）本身。\n\n```typescript\nasync function  createServer({ port = 3000 }: ServerConfig = {}): Promise<Server> {\n    // do something\n}\n```\n\n### 添加新命令test\n\n```json\n{\n    \"script\": {\n+        \"test\": \"yarn build && jest\"\n    },\n+   \"jest\": {\n+        \"watchPathIgnorePatterns\": [\"<rootDir>/test/temp\"] // 告诉jest不需要运行这个目录下的.test\n+   }\n}\n```\n\n### 观察测试得出的结论\n\n```execa```包: 创建子进程运行```./bin/vds.js```，并且等待进程在控制台打印输出```Running```后，进行测试，在所有测试完成后终止其进程。\n\n```puppeteer```: 可以通过API来控制Chrome/Chromium，很多操作浏览器的行为都可以通过```puppeteer```来实现。\n\n#### 流程查看（后续再画出流程图指）\n\n这里的流程查看，可以使用单元测试运行，但是还有一种形式。\n\n1.进入 ```USER/test/fixtures```目录，运行```USER/bin/vds.js```。\n2.打开```localhost:3000```。\n3.可以观察流程，修改```fixtures```文件夹下的组件（注意要恢复，因为该文件会影响测试结果，导致后续jest测试失败），查看```ws```发送的通知。\n\n#### HMR文件是何时植入的(就是client.ts是什么时候给到浏览器进行ws的建立的)？\n\n``````javascript\n#index.html\n<div id=\"app\"></div>\n<script type=\"module\" src=\"/main.js\"></script>\n\n#main.js http://localhost:3000/main.js\n// 经过server/server.ts\n// 识别到请求的是（注意哈，当前工作目录在fixtures目录下）main.js，js文件不进行分块发送（commit-4中有提到）\n// 直接发送main.js\nimport { createApp } from 'vue' // 等待加载完毕文件\nimport Comp from './Comp.vue' // 才会运行js，别忘了esm的特性是静态的（除了import.resolve()）\ncreateApp(Comp).mount('#app')\n\n#server/moduleMiddleware.ts的拦截（http://localhost:3000/__modules/vue）\n// 拦截后这里直接发送一整个 'dist/vue.runtime.esm-browser.js'(怎么寻找的？看commit-4)\n\n# server/vueMiddleware.ts (http://localhost:3000/Comp.vue)\nif (!query.type) { // 并没有type参数，什么参数都没\n    // inject hmr client 植入hmr\n    let code = `import \"/__hmrClient\"\\n`\n    if (descriptor.script) {\n      code += rewrite(\n        descriptor.script.content,\n        true /* rewrite default export to `script` */\n      )\n    } else {\n      code += `const __script = {}; export default __script`\n    }\n    if (descriptor.template) {\n      code += `\\nimport { render as __render } from ${JSON.stringify(\n        parsed.pathname + `?type=template${query.t ? `&t=${query.t}` : ``}`\n      )}`\n      code += `\\n__script.render = __render`\n    }\n    if (descriptor.style) { // 还没做\n      // TODO\n    }\n    code += `\\n__script.__hmrId = ${JSON.stringify(parsed.pathname)}`\n    return sendJS(res, code)\n}\n\n# http://localhost:3000/Comp.vue\nimport \"/__hmrClient\"\nimport Child from './Child.vue'\nlet __script; export default (__script = {\n  components: { Child },\n  setup() {\n    return {\n      count: 0\n    }\n  }\n})\nimport { render as __render } from \"/Comp.vue?type=template\"\n__script.render = __render\n__script.__hmrId = \"/Comp.vue\"\n// （这块其实就是你写的vue组件，帮你设置好输出罢了）\n\n# http://localhost:3000/Child.vue\nimport \"/__hmrClient\" // 二次导入为什么不会进行两次ws的连接？（因为import只进行一次的，esm它不同amd cmd cjs）\nconst __script = {}; export default __script\nimport { render as __render } from \"/Child.vue?type=template\"\n__script.render = __render\n__script.__hmrId = \"/Child.vue\"\n// 与Comp一样\n``````\n\n#### 为什么render要分开引入?\n\n```import \"/Child.vue?type=template\"```的好处分析：\n\n##### 关于```reload```事件\n\n这里如果你的vue组件改变，```server/hmrWatcher```中会对比新旧AST语法树中的```script```字符，如果是script改变，那么发送```reload```事件，给用户端。客户端识别到```reload```事件，他将使用```esm```的异步引入模块```import().then()```。\n\n```javascript\n# client/client.ts (我看的是未经编译的ts，实际是js,这里涉及到的其实都是js，为了方便我用了ts)\ncase 'reload':\n      import(`${path}?t=${Date.now()}`).then((m) => {\n        __VUE_HMR_RUNTIME__.reload(path, m.default) // path资源路径\n        console.log(`[vds] ${path} reloaded.`)\n      })\n      break\n```\n\n```__VUE_HMR_RUNTIME__```: \n\n先介绍一下vue-next的知识点。\n\n```javascript\n# vue-next/runtime-core/mountComponent: Function\n// 这个在vue组件开始渲染的时候触发，在mountComponent Component类型组件才会有\nif (__DEV__ && instance.type.__hmrId) { // 可以看到需要有__hmrId这个文件路径才会帮你注册\n      registerHMR(instance) // 注册hmrID 其实就是路径\n}\n\n# vue-next/runtime-core/unmountComponent: Function\n// 删除注册\nif (__DEV__ && instance.type.__hmrId) {\n      unregisterHMR(instance)\n}\n\n# vue-next/runtime-core/hmr.ts\nexport function registerHMR(instance: ComponentInternalInstance) {\n  const id = instance.type.__hmrId!\n  let record = map.get(id)\n  if (!record) {\n    createRecord(id) // map中存放set, key: hmrId, value: new Set()\n    record = map.get(id)!\n  }\n  record.add(instance) // 把instance 存进set 一个组件可能会被多处地方使用，所以会存在多个instance\n}\n    \nexport function unregisterHMR(instance: ComponentInternalInstance) {\n  map.get(instance.type.__hmrId!)!.delete(instance)\n}\n```\n\n这里使用到了的功能是```reload```:\n\n```typescript\nexport const hmrDirtyComponents = new Set<Component>()\n\nfunction reload(id: string, newComp: ComponentOptions) {\n  const record = map.get(id)\n  if (!record) return\n  Array.from(record).forEach(instance => {\n    const comp = instance.type // instance.type是旧的newComp\n    if (!hmrDirtyComponents.has(comp)) {\n      // 1. Update existing comp definition to match new one\n      extend(comp, newComp)\n      for (const key in comp) {\n        if (!(key in newComp)) {\n          delete (comp as any)[key]\n        }\n      }\n        \n      // 2. Mark component dirty. This forces the renderer to replace the component\n      // on patch.\n      hmrDirtyComponents.add(comp)\n      // 3. Make sure to unmark the component after the reload.\n      queuePostFlushCb(() => {\n        hmrDirtyComponents.delete(comp)\n      })\n    }\n\n    if (instance.parent) {\n      // 4. Force the parent instance to re-render. This will cause all updated\n      // components to be unmounted and re-mounted. Queue the update so that we\n      // don't end up forcing the same parent to re-render multiple times.\n      queueJob(instance.parent.update)\n    } else if (instance.appContext.reload) {\n      // root instance mounted via createApp() has a reload method\n      instance.appContext.reload()\n    } else if (typeof window !== 'undefined') {\n      // root instance inside tree created via raw render(). Force reload.\n      window.location.reload() //\n    } else {\n      console.warn(\n        '[HMR] Root or manually mounted instance modified. Full reload required.'\n      )\n    }\n  })\n}\n```\n\n这块检测了组件字段的改变。如果当前组件拥有父组件，调用父组件的```instance.update```方法:![image-20210402102925225](./instance-update.png)\n\n(这里真心建议大家看一下vue-next的渲染流程，不然我也表达不出它原本意思)\n\n[vue-next组件渲染流程图](https://www.processon.com/view/link/5f85c9321e085307a0892f7e)\n\n调用这个方法，目标就是让子组件重新走一次流程，可以让render重新更新，让视图重新抓取effect，大体意思就是渲染子组件。\n\n调用这个方法，目标就是让子组件重新走一次流程，可以让render重新更新，让视图重新抓取effect，大体意思就是渲染子组件。\n\n如果没有子组件，那么调用```instance.appContext.reload```：\n\n```typescript\nexport interface AppContext {\n  config: AppConfig\n  mixins: ComponentOptions[]\n  components: Record<string, PublicAPIComponent>\n  directives: Record<string, Directive>\n  provides: Record<string | symbol, any>\n  reload?: () => void // HMR only\n}\n\n// main.js（我们vue项目的入口文件） 中的 createApp(Comp).mount('#app')\nfunction mount(rootContainer: HostElement, isHydrate?: boolean): any {\n        if (!isMounted) {\n          const vnode = createVNode(Comp as Component, rootProps)\n          vnode.appContext = context\n          // HMR root reload for reload\n          if (__DEV__) {\n            context.reload = () => {\n              render(cloneVNode(vnode), rootContainer)\n            }\n          }\n}\n```\n\n所以看到，本质上就是调用render，这和一整个vue渲染流程是一样的。\n\n![render](./render.png)\n\n[^vue-next组件渲染流程图]: [链接](https://www.processon.com/view/link/5f85c9321e085307a0892f7e)\n\n如果也没有```reload```，执行```window.loaction.reload()```（没有分析出什么环境才会触发，毕竟dev环境下，必有reload，留个坑，暂时认为是```vue-next```的保守行为）\n\n##### 关于```rerender事件```：\n\n文件改动到的仅仅是模板（```template```），像对比```script```那样，如果有变动```server```端则发送一个```rerender```事件到```client```客户端。```client```客户端收到```rerender```，触发```__VUE_HMR_RUNTIME__.rerender```。\n\n```javascript\n# client/client.ts    \ncase 'rerender':\n      import(`${path}?type=template&t=${Date.now()}`).then((m) => {\n        __VUE_HMR_RUNTIME__.rerender(path, m.render)\n        console.log(`[vds] ${path} template updated.`)\n      })\n      break\n```\n\n接着查看```vue-next```的```rerender```方法:\n\n```javascript\n# vue-next/runtime-core/hmr.ts\nfunction rerender(id: string, newRender?: Function) { // id为path路径\n  const record = map.get(id)\n  if (!record) return\n  // Array.from creates a snapshot which avoids the set being mutated during\n  // updates\n  Array.from(record).forEach(instance => {\n    if (newRender) {\n      instance.render = newRender as InternalRenderFunction // 更新vnode\n    }\n    instance.renderCache = []\n    // this flag forces child components with slot content to update\n    isHmrUpdating = true\n    instance.update() // 调用自身update\n    isHmrUpdating = false\n  })\n}\n```\n\n#### 结论\n\n```rerender```用在```template```，使用```instance.update```。\n\n```reload```用在```script```，使用```instance.parent.update```/```render```。\n\n```script```包含了```template```的更新，分开的做法，好处在```template```更新的时候，只更新组件自身的树，```script```是从父树开始的。\n\n##### 为什么```script```需要从```parent```更新？\n\n查看组件渲染的三大块:\n\n1.```createComponentInstance``` 创建```instance```，一个组件相关的```Object```。\n\n2.```setupComponent```，对attrs的一些处理 还有```setup```的处理```instance.type```中的所有关键词字段的处理。\n\n3.```setupRenderEffect```，2xOptionsAPI与```render```的处理。\n\n因为```script```的改变，会有可能变动到功能点```2```，但是调用父组件的更新？不是只会走```updateComponent```的参数更新功能？\n\n```javascript\n# vue-next/runtime-core/vnode.ts\nexport function isSameVNodeType(n1: VNode, n2: VNode): boolean {\n  if (\n    __DEV__ &&\n    n2.shapeFlag & ShapeFlags.COMPONENT &&\n    hmrDirtyComponents.has(n2.type as Component) // reload: 已经在渲染前运行\n  ) {\n    // HMR only: if the component has been hot-updated, force a reload.\n    return false\n  }\n  return n1.type === n2.type && n1.key === n2.key\n}\n```\n\n可以看到不走```updateComponent```，强行走渲染```1```，```2```，```3```的流程。\n\n所以可以得出：这是为了优化```template```，不需要走上级的更新。\n\n##### 为什么要走重新渲染呢？不是万物皆render?\n\n不是这样说的，```template```的改变，只会影响到你的参数，而实际上render又是script变动的一部分，在手写```render```的情况下，是走重新渲染的（不探讨为什么手写render也要走重新渲染，我目前觉得不需要重新渲染，vite可以优化一下）。\n\n假如你的```setup```的内容改变，需要重新运行，那么就要经过```2```的处理，所以就需要重新渲染。\n\n\n\n# 7 - 3e64a74 去除git add\n\n### 更新package.json\n\n```json\n{\n  \"lint-staged\": {\n    \"*.js\": [\n-     \"git add\", \n      \"prettier --write\"\n    ],\n    \"*.ts\": [\n-     \"git add\", \n      \"prettier --parser=typescript --write\"\n    ]\n  }\n}\n```\n\n去除```git add```，现在把改动文件丢进暂存区，将不会把你的文件格式化了。在```commit```后，你会发现进行了格式化。\n可以自己尝试一下把某个单括号改成双括号，你会发现```commit```提交后自动变成单括号了。\n\n\n\n# 8 - 93286b9 优化cwd\n\n### 优化process.cwd(）\n\n提取成为一个参数，作为输入。\n\n### 更改名称与整合功能\n\n处理模块中间件的文件```moduleMiddleware.ts```修改名称为```moduleResolve.ts```，并且新增寻找```module```路径与模块名称的键值对。为```source map```做准备。\n\n```typescript\n// TODO support custom imports map e.g. for snowpack web_modules\nconst fileToIdMap = new Map()\n\n  if (id.endsWith('.map')) {\n    sourceMapPath = id\n    id = fileToIdMap.get(id.replace(/\\.map$/, ''))\n    if (!id) {\n      res.statusCode = 404\n      res.end()\n      return\n    }\n  }\n\ntry {\n    modulePath = resolve(cwd, `${id}/package.json`)\n    if (id === 'vue') {\n    } else {\n      fileToIdMap.set(path.basename(modulePath), id)\n    }\n    sendJSStream(res, modulePath)\n  } catch (e) {\n  }\n```\n\n更改带有处理vue文件并生成语法树功能的```parseSFC.ts```名称为```vueCompiler.ts```\n\n修改监听功能```hmrWatcher.ts```文件名称为```watcher.ts```\n\n### 更改resolve-cwd为resolve-from\n\n在```moduleResolver```，中会寻找包。\n\n```resolve-cwd```: 从当前命令行路径开始寻找。\n\n```resolve-from```: 从给出的路径开始寻找。\n\n以上两个寻找行为与require.resolve一致，仅仅是入口方式不同。\n\n\n\n# 9 - f4382f1 整合优化\n\n### 整理```vueCompiler.ts```\n\n把处理```template```与```script```的代码，提取出来。\n\n```function compileSFCTemplate``` and ```function compileSFCMain```\n\n### watcher.ts\n\n如果没有AST语法树，则不做任何处理。\n\n```typescript\nif (file.endsWith('.vue')) {\n      // check which part of the file changed\n      const [descriptor, prevDescriptor] = await parseSFC(file)\n-      if (!prevDescriptor) {\n+      if (!descriptor || !prevDescriptor) {\n        // the file has never been accessed yet\n        return\n      }\n}\n```\n\n\n\n# 10 - 140f2b2 style HMR\n\n### 新增事件\n\n原有事件：\n```reload```：```<script>```的更新，会触发此事件（拥有```scoped```的样式也会触发，这里只留下坑位）\n```rerender```：```<template>```的更新，会触发此事件。\n\n新增：\n```style-update```：发送给```client```更新```css```的消息。\n\n```typescript\nnextStyles.forEach((_, i) => {\n        if (!prevStyles[i] || !isEqual(prevStyles[i], nextStyles[i])) {\n          send({\n            type: 'style-update',\n            path: resourcePath,\n            index: i\n          })\n        }\n})\n```\n\n```style-remove```：发送给```client```删除```css```的消息\n\n### ```hash-sum```包\n\n生成hash，例如```hash_sum(resourcePath)```，```style-remove```事件需要用到，识别对应```vue```组件的```id```。\n\n### ```watcher.ts```\n\n整合代码，包括前面说的新增两个事件。\n\n### ```client.ts```\n\n新增事件，同上。\n\n### ```vueCompiler.ts```\n\n新增对```style```的处理：\n\n```typescript\nif (descriptor.styles) {\n    descriptor.styles.forEach((s, i) => {\n      if (s.scoped) hasScoped = true\n      code += `\\nimport ${JSON.stringify(\n        pathname + `?type=style&index=${i}${timestamp}`\n      )}`\n    })\n    if (hasScoped) {\n      code += `\\n__script.__scopeId = \"data-v-${hash(pathname)}\"`\n    }\n}\n\n\nfunction compileSFCStyle(\n  res: ServerResponse,\n  style: SFCStyleBlock,\n  index: string,\n  filename: string,\n  pathname: string\n) {\n  const id = hash(pathname)\n  const { code, errors } = compileStyle({\n    source: style.content,\n    filename,\n    id: `data-v-${id}`,\n    scoped: style.scoped != null\n  })\n  // TODO css modules\n\n  if (errors) {\n    // TODO\n  }\n  sendJS(\n    res,\n    `\nconst id = \"vue-style-${id}-${index}\"\nlet style = document.getElementById(id)\nif (!style) {\n  style = document.createElement('style')\n  style.id = id\n  document.head.appendChild(style)\n}\nstyle.textContent = ${JSON.stringify(code)}\n  `.trim()\n  )\n}\n```\n\n遇到vue的```AST```的```style```，检测```<style>```是否添加了```scoped```（因为一个组件可以写多个```<style>```，所以用了```forEach```），如果有则：\n\n```typescript\nimport hash from 'hash-sum'\nif (hasScoped) {\n      code += `\\n__script.__scopeId = \"data-v-${hash(pathname)}\"`\n}\n```\n\n以上```style```功能还没有测试用例完善。"
  },
  {
    "path": "temp/temp.md",
    "content": ""
  }
]