GVKun编程网logo

深度分析前端构建工具:Vite2 v.s Snowpack3 v.s. Webpack5(前端构建工具深度剖析)

13

对于想了解深度分析前端构建工具:Vite2v.sSnowpack3v.s.Webpack5的读者,本文将是一篇不可错过的文章,我们将详细介绍前端构建工具深度剖析,并且为您提供关于Turbopack发布

对于想了解深度分析前端构建工具:Vite2 v.s Snowpack3 v.s. Webpack5的读者,本文将是一篇不可错过的文章,我们将详细介绍前端构建工具深度剖析,并且为您提供关于Turbopack 发布后的各方反应:Vite/Webpack/围观群众、vue开发工具node.js及构建工具webpack、webpack 4.x 解决 webpack-dev-server工具在webpack构建的项目中使用问题、Webpack PK Vite,2021谁是下一代的前端构建工具的有价值信息。

本文目录一览:

深度分析前端构建工具:Vite2 v.s Snowpack3 v.s. Webpack5(前端构建工具深度剖析)

深度分析前端构建工具:Vite2 v.s Snowpack3 v.s. Webpack5(前端构建工具深度剖析)

而在Vite之前,还有SNowpack也同样采用了No-Bundler构建方案。那么No-Bundler模式与传统老牌构建工具Webpack孰优孰劣呢?能否实现平滑迁移和完美取代?

下面就带着问题一起分析一下Vite2、SNowpack3和Webpack5吧!

Webpack

Webpack是近年来使用量最大,同时社区最完善的前端打包构建工具,5.x版本对构建细节进行了优化,某些场景下打包速度提升明显,但也没能解决之前一直被人诟病的大项目编译慢的问题,这也和Webpack本身的机制相关。

已经有很多文章讲解Webpack的运行原理了,本文就不再赘述,我们着重分析一下后起之秀。

SNowpack

什么是SNowpack?

首次提出利用浏览器原生ESM能力的工具并非是Vite,而是一个叫做Snowpack的工具。前身是@pika/web,从1.x版本开始更名为SNowpack。

SNowpack在其官网是这样进行自我介绍的:“SNowpack是一种闪电般快速的前端构建工具,专为现代Web设计。 它是开发工作流程较重,较复杂的打包工具(如Webpack或Parcel)的替代方案。SNowpack利用JavaScript的本机模块系统(称为ESM)来避免不必要的工作并保持流畅的开发体验”。 

SNowpack的理念是减少或避免整个bundle的打包,每次保存单个文件时,传统的JavaScript构建工具(例如Webpack和Parcel)都需要重新构建和重新打包应用程序的整个bundle。重新打包时增加了在保存更改和看到更改反映在浏览器之间的时间间隔。在开发过程中, SNowpack为你的应用程序提供unbundled server。每个文件只需要构建一次,就可以永久缓存。文件更改时,SNowpack会重新构建该单个文件。在重新构建每次变更时没有任何的时间浪费,只需要在浏览器中进行HMR更新。

再了解一下发明SNowpack的团队pika,pika团队有一个宏伟的使命:让Web应用提速90%:

image.png

为此,pika团队开发并维护了两个技术体系:构建相关的SNowpack和造福大众的Skypack。

在这里我们简单聊一下Skypack的初衷,当前许多Web应用都是在不同NPM包的基础上进行构建的,而这些NPM包都被Webpack之类的打包工具打成了一个bundle,如果这些NPM包都来源于同一个CDN地址,且支持跨域缓存,那么这些NPM包在缓存生效期内都只需要加载一次,其他网站用到了同样的NPM包,就不需要重新下载,而是直接读取本地缓存。

例如,智联的官网与B端都是基于vue+vuex开发的,当HR在B端发完职位后,进入官网预览自己的公司对外主页都不用重新下载,只需要下载智联官网相关的一些业务代码即可。为此,pika专门建立了一个CDN(Skypack)用来下载NPM上的ESM模块。

后来SNowpack发布的时候,pika团队顺便发表了一篇名为《A Future Without Webpack》 的文章,告诉大家可以尝试抛弃Webpack,采用全新的打包构建方案,下图取自其官网,展示了bundled与unbundled之间的区别。

image.png

在HTTP/2和5G网络的加持下,我们可以预见到HTTP请求数量不再成为问题,而随着Web领域新标准的普及,浏览器也在逐步支持ESM(<script module>)。

image.png

源码分析

启动构建时会调用源码src/index.ts中的cli方法,该方法的代码删减版如下:

import {command as buildCommand} from './commands/build';

export async function cli(args: string[]) {
  const cliFlags = yargs(args, {
    array: ['install', 'env', 'exclude', 'external']
  }) as CLIFlags;

  if (cmd === 'build') {
    await buildCommand(commandOptions);
    return process.exit(0);
  }
} 

进入commands/build文件,执行大致逻辑如下:

export async function build(commandOptions: CommandOptions): Promise<SNowpackbuildresult> {
  // 读取config代码
  // ...
  for (const runPlugin of config.plugins) {
    if (runPlugin.run) {
      // 执行插件
    }
  }
  
  // 将 `import.Meta.env` 的内容写入文件
  await fs.writeFile(
    path.join(internalFilESbuildLoc, 'env.js'),
    generateEnvModule({mode: 'production', isSSR}),
  );

  // 如果 HMR,则加载 hmr 工具文件
  if (getIsHmrEnabled(config)) {
    await fs.writeFile(path.resolve(internalFilESbuildLoc, 'hmr-client.js'), HMR_CLIENT_CODE);
    await fs.writeFile(
      path.resolve(internalFilESbuildLoc, 'hmr-error-overlay.js'),
      HMR_OVERLAY_CODE,
    );
    hmrEngine = new EsmHmrEngine({port: config.devOptions.hmrPort});
  }
 
  // 开始构建源文件
  logger.info(colors.yellow('! building source files...'));
  const buildStart = performance.Now();
  const buildPipelineFiles: Record<string, FileBuilder> = {};

  // 根据主 buildPipelineFiles 列表安装所有需要的依赖项,对应下面第三部
  async function installDependencies() {
    const scannedFiles = Object.values(buildPipelineFiles)
      .map((f) => Object.values(f.filesToResolve))
      .reduce((flat, item) => flat.concat(item), []);

    // 指定安装的目标文件夹
    const installDest = path.join(buildDirectoryLoc, config.buildOptions.MetaUrlPath, 'pkg');

    // installOptimizedDependencies 方法调用了 esinstall 包,包内部调用了 rollup 进行模块分析及 commonjs 转 esm
    const installresult = await installOptimizedDependencies(
      scannedFiles,
      installDest,
      commandOptions,
    );

    return installresult
  }

  // 下面因代码繁多,仅展示源码中的注释
  // 0. Find all source files.
  // 1. Build all files for the first time, from source.
  // 2. Install all dependencies. This gets us the import map we need to resolve imports.
  // 3. Resolve all built file imports.
  // 4. Write files to disk.
  // 5. Optimize the build.

  // "--watch" mode - Start watching the file system.
  // Defer "chokidar" loading to here, to reduce impact on overall startup time
  logger.info(colors.cyan('watching for changes...'));
  const chokidar = await import('chokidar');

  // 本地文件删除时清除 buildPipelineFiles 对应的文件
  function onDeleteEvent(fileLoc: string) {
    delete buildPipelineFiles[fileLoc];
  }

  // 本地文件创建及修改时触发
  async function onWatchEvent(fileLoc: string) {
    // 1. Build the file.
    // 2. Resolve any ESM imports. Handle new imports by triggering a re-install.
    // 3. Write to disk. If any proxy imports are needed, write those as well.

    // 触发 HMR
    if (hmrEngine) {
      hmrEngine.broadcastMessage({type: 'reload'});
    }
  }

  // 创建文件监听器
  const watcher = chokidar.watch(Object.keys(config.mount), {
    ignored: config.exclude,
    ignoreInitial: true,
    persistent: true,
    disableGlobbing: false,
    useFsEvents: isFsEventsEnabled(),
  });
  watcher.on('add', (fileLoc) => onWatchEvent(fileLoc));
  watcher.on('change', (fileLoc) => onWatchEvent(fileLoc));
  watcher.on('unlink', (fileLoc) => onDeleteEvent(fileLoc));

  // 返回一些方法给 plugin 使用
  return {
    result: buildresultManifest,
    onFileChange: (callback) => (onFileChangeCallback = callback),
    async shutdown() {
      await watcher.close();
    },
  };
}

export async function command(commandOptions: CommandOptions) {
  try {
    await build(commandOptions);
  } catch (err) {
    logger.error(err.message);
    logger.debug(err.stack);
    process.exit(1);
  }

  if (commandOptions.config.buildOptions.watch) {
    // We intentionally never want to exit in watch mode!
    return new Promise(() => {});
  }
} 

所有的模块会经过install进行安装,此处的安装是将模块转换成ESM放在pkg目录下,并不是npm包安装的概念。

在SNowpack3中增加了一些老版本不支持的能力,如:内部默认集成Node服务、支持CSS Modules、支持HMR等。

Vite

什么是Vite?

Vite(法语单词“ fast”,发音为/vit/)是一种构建工具,旨在为现代Web项目提供更快,更精简的开发体验。它包括两个主要部分:

  1. 开发服务器,它在本机ESM上提供了丰富的功能增强,例如,极快的Hot Module Replacement(HMR)。
  2. 构建命令,它将代码使用Rollup进行构建。

随着vue3的推出,Vite也随之成名,起初是一个针对Vue3的打包编译工具,目前2.x版本发布面向了任何前端框架,不局限于Vue,在Vite的README中也提到了在某些想法上参考了SNowpack。

在已有方案上Vue本可以抛弃Webpack选择SNowpack,但选择开发Vite来造一个新的轮子也有Vue团队自己的考量。

在Vite官方文档列举了Vite与SNowpack的异同,其实本质是说明Vite相较于SNowpack的优势。

相同点,引用Vite官方的话:

Snowpack is also a no-bundle native ESM dev server that is very similar in scope to Vite。

不同点:

  1. SNowpack的build默认是不打包的,好处是可以灵活选择Rollup、Webpack等打包工具,坏处就是不同打包工具带来了不同的体验,当前ESbuild作为生产环境打包尚不稳定,Rollup也没有官方支持SNowpack,不同的工具会产生不同的配置文件;
  2. Vite支持多page打包;
  3. Vite支持Library Mode;
  4. Vite支持CSS代码拆分,SNowpack默认是CSS in JS;
  5. Vite优化了异步代码加载;
  6. Vite支持动态引入 polyfill;
  7. Vite官方的 legacy mode plugin,可以同时生成 ESM 和 NO ESM;
  8. First Class Vue Support。

第5点Vite官网有详细介绍,在非优化方案中,当A导入异步块时,浏览器必须先请求并解析,A然后才能确定它也需要公共块C。这会导致额外的网络往返:

Entry ---> A ---> C

Vite通过预加载步骤自动重写代码拆分的动态导入调用,以便在A请求时并行C获取:

Entry ---> (A + C)

可能C会多次导入,这将导致在未优化的情况下发出多次请求。Vite的优化将跟踪所有import,以完全消除重复请求,示意图如下:

image.png

第8点的First Class Vue Support,虽然在列表的最后一项,实则是点睛之笔。

源码分析

Vite在启动时,如果不是中间件模式,内部默认会启一个http server。

export async function createServer(
  inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
  // 获取 config
  const config = await resolveConfig(inlineConfig, 'serve', 'development')
  const root = config.root
  const serverConfig = config.server || {}
  
  // 判断是否是中间件模式
  const middlewareMode = !!serverConfig.middlewareMode
  const middlewares = connect() as Connect.Server
  
  // 中间件模式不创建 http 服务,允许外部以中间件形式调用:https://Vitejs.dev/guide/api-javascript.html#using-the-Vite-server-as-a-middleware
  const httpServer = middlewareMode
    ? null
    : await resolveHttpServer(serverConfig, middlewares)
  
  // 创建 websocket 服务
  const ws = createWebSocketServer(httpServer, config)
  
  // 创建文件监听器
  const { ignored = [], ...watchOptions } = serverConfig.watch || {}
  const watcher = chokidar.watch(path.resolve(root), {
    ignored: ['**/node_modules/**', '**/.git/**', ...ignored],
    ignoreInitial: true,
    ignorePermissionErrors: true,
    ...watchOptions
  }) as FSWatcher


  const plugins = config.plugins
  const container = await createPluginContainer(config, watcher)
  const moduleGraph = new ModuleGraph(container)
  const closeHttpServer = createSeverCloseFn(httpServer)

  const server: ViteDevServer = {
    // 前面定义的常量,包含:config、中间件、websocket、文件监听器、ESbuild 等
  }

  // 监听进程关闭
  process.once('SIGTERM', async () => {
    try {
      await server.close()
    } finally {
      process.exit(0)
    }
  })

  watcher.on('change', async (file) => {
    file = normalizePath(file)

    // 文件更改时使模块图缓存无效
    moduleGraph.onFileChange(file)

    if (serverConfig.hmr !== false) {
      try {
        // 大致逻辑是修改 env 文件时直接重启 server,根据 moduleGraph 精准刷新,必要时全部刷新
        await handleHMRUpdate(file, server)
      } catch (err) {
        ws.send({
          type: 'error',
          err: prepareError(err)
        })
      }
    }
  })

  // 监听文件创建
  watcher.on('add', (file) => {
    handleFileAddUnlink(normalizePath(file), server)
  })

  // 监听文件删除
  watcher.on('unlink', (file) => {
    handleFileAddUnlink(normalizePath(file), server, true)
  })
  
  // 挂载插件的服务配置钩子
  const postHooks: ((() => void) | void)[] = []
  for (const plugin of plugins) {
    if (plugin.configureServer) {
      postHooks.push(await plugin.configureServer(server))
    }
  }

  // 加载多个中间件,包含 cors、proxy、open-in-editor、静态文件服务等

  // 运行post钩子,在html中间件之前应用的,这样外部中间件就可以提供自定义内容取代 index.html
  postHooks.forEach((fn) => fn && fn())

  if (!middlewareMode) {
    // 转换 html
    middlewares.use(indexHtmlMiddleware(server, plugins))
    // 处理 404
    middlewares.use((_, res) => {
      res.statusCode = 404
      res.end()
    })
  }

  // errorHandler 中间件
  middlewares.use(errorMiddleware(server, middlewareMode))

  // 执行优化逻辑
  const runoptimize = async () => {
    if (config.optimizeCacheDir) {
      // 将使用 ESbuild 将依赖打包并写入 node_modules/.Vite/xxx
      await optimizeDeps(config)
      // 更新 Metadata 文件
      const dataPath = path.resolve(config.optimizeCacheDir, 'Metadata.json')
      if (fs.existsSync(dataPath)) {
        server._optimizeDepsMetadata = JSON.parse(
          fs.readFileSync(dataPath, 'utf-8')
        )
      }
    }
  }

  if (!middlewareMode && httpServer) {
    // 在服务器启动前覆盖listen方法并运行优化器
    const listen = httpServer.listen.bind(httpServer)
    httpServer.listen = (async (port: number, ...args: any[]) => {
      await container.buildStart({})
      await runoptimize()
      return listen(port, ...args)
    }) as any

    httpServer.once('listening', () => {
      // 更新实际端口,因为这可能与初始端口不同
      serverConfig.port = (httpServer.address() as AddressInfo).port
    })
  } else {
    await runoptimize()
  }

  // 最后返回服务
  return server
} 

访问Vite服务的时候,默认会返回index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script type="module" src="/@Vite/client"></script>
    <Meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <Meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html> 

处理import文件逻辑,在node/plugins/importAnalysis.ts文件内:

export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
  const clientPublicPath = path.posix.join(config.base, CLIENT_PUBLIC_PATH)
  let server: ViteDevServer
  return {
    name: 'Vite:import-analysis',
    configureServer(_server) {
      server = _server
    },
    async transform(source, importer, ssr) {
 const rewriteStart = Date.Now()
 // 使用 es-module-lexer 进行语法解析

      await init
      let imports: ImportSpecifier[] = []
      try {
        imports = parseImports(source)[0]
      } catch (e) {
        const isVue = importer.endsWith('.vue')
        const maybeJSX = !isVue && isJSRequest(importer)


        // 判断文件后缀给不同的提示信息
        const msg = isVue
          ? `Install @Vitejs/plugin-vue to handle .vue files.`
          : maybeJSX
          ? `If you are using JSX, make sure to name the file with the .jsx or .tsx extension.`
          : `You may need to install appropriate plugins to handle the ${path.extname(
              importer
            )} file format.`
        this.error(
          `Failed to parse source for import analysis because the content ` +
            `contains invalid JS Syntax. ` +
            msg,
          e.idx
        )
      }

      // 将代码字符串取出
      let s: MagicString | undefined
      const str = () => s || (s = new MagicString(source))

      // 解析 env、glob 等并处理
      // 转换 cjs 成 esm
    }
  }
} 

拿Vue的NPM包举例经优化器处理后的路径如下:

// before
- import { createApp } from 'vue'
+ import { createApp } from '/node_modules/.Vite/vue.runtime.esm-bundler.js?v=d17c1aa4'
import App from '/src/App.vue'

createApp(App).mount('#app') 

image.png

截图中的/src/App.vue路径经过Vite处理发生了什么?

首先需要引用 @Vitejs/plugin-vue 来处理,内部使用Vue官方的编译器@vue/compiler-sfc,plugin处理逻辑同rollup的plugin,Vite在Rollup的插件机制上进行了扩展。

详细可参考:https://Vitejs.dev/guide/api-plugin.html,这里不做展开。

编译后的App.vue文件如下:

import { createHotContext as __Vite__createHotContext } from "/@Vite/client";
import.Meta.hot = __Vite__createHotContext("/src/App.vue");
import HelloWorld from '/src/components/HelloWorld.vue'

const _sfc_main = {
  expose: [],
  setup(__props) {
    return { HelloWorld }
  }
}

import { 
  createVNode as _createVNode, 
  Fragment as _Fragment, 
  openBlock as _openBlock, 
  createBlock as _createBlock 
} from "/node_modules/.Vite/vue.runtime.esm-bundler.js?v=d17c1aa4"

const _hoisted_1 = /*#__PURE__*/_createVNode("img", {
  alt: "Vue logo",
  src: "/src/assets/logo.png"
}, null, -1 /* HOISTED */)

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _hoisted_1,
    _createVNode($setup["HelloWorld"], { msg: "Hello Vue 3 + Vite" })
  ], 64 /* STABLE_FRAGMENT */))
}

import "/src/App.vue?vue&type=style&index=0&lang.css"

_sfc_main.render = _sfc_render
_sfc_main.__file = "/Users/orange/build/Vite-vue3/src/App.vue"
export default _sfc_main
_sfc_main.__hmrId = "7ba5bd90"
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
import.Meta.hot.accept(({ default: updated, _rerender_only }) => {
  if (_rerender_only) {
    __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
  } else {
    __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
  }
}) 

可以发现,Vite本身并不会递归编译,这个过程交给了浏览器,当浏览器运行到import HelloWorld from '/src/components/HelloWorld.vue' 时,又会发起新的请求,通过中间件来编译 vue,以此类推,为了证明我们的结论,可以看到 HelloWorld.vue 的请求信息:

image.png

经过分析源码后,能断定的是,SNowpack与Vite在启动服务的时间会远超Webpack,但复杂工程的首次编译到完全可运行的时间需要进一步测试,不同场景下可能产生截然不同的结果。

功能对比

 Vite@2.0.3Webpack@5.24.2SNowpack@3.0.13
支持Vue2 非官方支持: https://github.com/underfin/vite-plugin-vue2 支持:vue-loader@^15.0.0 非官方支持: https://www.npmjs.com/package/@lepzulnag/Snowpack-plugin-vue-2
支持Vue3 支持 支持:vue-loader@^16.0.0(https://github.com/Jamie-Yang/vue3-boilerplate) 支持: https://www.npmjs.com/package/@Snowpack/plugin-vue
支持Typescript 支持:ESbuild (默认无类型检查) 支持:ts-loader 支持: https://github.com/Snowpackjs/Snowpack/tree/main/create-Snowpack-app/app-template-vue-typescript
支持CSS预处理器 支持: https://vitejs.dev/guide/features.html#css-pre-processors 支持:https://vue-loader.vuejs.org/guide/pre-processors.html 部分支持:官方仅提供了Sass和Postcss,且存在未解决BUG
支持CSS Modules 支持: https://vitejs.dev/guide/features.html#css-modules 支持:https://vue-loader.vuejs.org/guide/css-modules.html 支持
支持静态文件 支持 支持 支持
开发环境 no-bundle native ESM(CJS → ESM) bundle(CJS/UMD/ESM) no-bundle native ESM(CJS → ESM)
HMR 支持 支持 支持
生产环境 Rollup Webpack Webpack, Rollup, or even ESbuild
Node API 调用能力 支持 支持 支持

启动时编译速度对比

下面一组测试的代码完全相同,都是Hellow World工程,没有任何复杂逻辑,Webpack与SNowpack分别引入了对应的Vue plugin,Vite无需任何插件。

Webpack5 + vue3(1.62s)

工程目录:

image.png

控制台输出:

image.png

SNowpack3 + vue3(2.51s)

工程目录:

image.png

控制台输出:

image.png

Vite2 + vue3(0.99s)

工程目录:

image.png

控制台输出:

image.png

真实项目迁移

测试案例:已存在的复杂逻辑vue工程

经过简单的测试及调研结果,首先从生态和性能上排除了SNowpack,下面将测试Webpack5与Vite2。

迁移Vite2遇到的问题:

1.不支持省略.vue后缀,因为此路由机制与编译处理强关联;

2.不支持.vue后缀文件内写jsx,若写jsx,需要改文件后缀为.jsx;

3.不建议import { ... } from "dayjs"与import duration from 'dayjs/plugin/duration'同时使用,从源码会发现在optimizeDeps阶段已经把ESM编译到了缓存文件夹,若同时使用会报错:

image.png

4.当optimizeDeps忽略后文件路径错误,node_modules/dayjs/dayjs.main.js?version=xxxxx,此处不应该在query中添加version;

5.组件库中window.$方法找不到,不能强依赖关联顺序,跟请求返回顺序有关;

6.当dependencies首次未被写入缓存时,补充写入会报错,需要二次重启;

image.png

7.在依赖关系复杂场景,Vue被多次cache,会出现ESM二次封装的情况,也就是ESM里面嵌套ESM的情况;

image.png

image.png

种种原因,调试到这里终结了,结论就是Vite2目前处于初期,尚不稳定,处理深层次依赖就目前的moduleGraph机制还有所欠缺,有待完善。

Webpack5

image.png

效果和我们之前测试相同代码在Webpack4下50+秒相比提升明显,实际场景可能存在误差,但WebpackConfig配置细节基本一致。

编译压缩提速

不知大家是否有遇到这个问题:

<--- Last few GCs --->
[59757:0x103000000] 32063 ms: Mark-sweep 1393.5 (1477.7) -> 1393.5 (1477.7) MB, 109.0 / 0.0 ms  allocation failure GC in old space requested
<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 0x24d9482a5ec1 <JSObject>
... 

或者在 92% 的进度里卡很久:

Webpack chunk asset optimization (92%) TerserPlugin

随着产物越来越大,编译上线和CI的时间都越来越长,而其中1/3及更多的时间则是在做压缩的部分。OOM的问题也通常来源于压缩。

如何解决压缩慢和占内存的问题,一直是逃避不开的话题,Vite采用了ESbuild,接下来分析一下ESbuild。

ESbuild

下面是官方的构建时间对比图,并没有说明场景,文件大小等,所以不具备实际参考价值。

image.png

之所以快,其中最主要的应该是用go写,然后编译为Native代码。然后npm安装时动态去下对应平台的二进制包,支持Mac、Linux和Windows,比如esbuild-darwin-64。

相同思路的还有es-module-lexer、swc等,都是用编译成Native代码的方式进行提速,用来弥补Node在密集cpu计算场景的短板。

ESbuild有两个功能,bundler和minifier。bundler的功能和babel以及Webpack相比差异很大,直接使用对现有业务的风险较大;而minifier可以尝试,在Webpack和babel产物的基础上做一次生产环境压缩,可以节省terser plugin的压缩时间。

同时针对Webpack提供了esbuild-webpack-plugin,可以在Webpack内直接使用ESbuild。

优缺点及总结

SNowpack

缺点:

  1. 社区不够完善,无法支撑我们后续的业务演进;
  2. 编译速度提效不明显。

Vite

优点:

  1. 因其与rollup联合,社区里rolllup的插件基本都可以直接使用,社区相对完善;
  2. 编译速度快。

缺点:

  1. 目前Vite处于2.0初期,BUG比较多;
  2. 本地的 ESbuild 与生产环境的babel编译结果差距较大,可能会导致功能差异。

Webpack5

优点:

  1. 从实际测试要比Webpack4快许多;
  2. 可借助ESbuild的代码压缩机制。

缺点:

  1. 相较 Vite 的本地开发编译速度有写不足(其实算不上缺点,因为解决了生产环境差异)。

回到我们文章开始的问题,经过上文的迁移测试环节,我们需要调整大量代码进行Vite迁移适配,由于原有Vue工程未遵循Vite的开发范式,迁移过程比较坎坷,新工程只要遵循Vite官方的开发文档,会规避上文提到的大部分问题。

所以已有Webpack工程迁移成本还是较大的,另一方面也是需要重点关注的就是本地开发与生产环境的差异问题,如果本机开发环境采用No-Bundle机制,而生产发布环境采用Bundle机制,这种不一致性会给测试和排查问题带来困扰和风险,在生态尚未齐备之前,建议审慎尝试。

Turbopack 发布后的各方反应:Vite/Webpack/围观群众

Turbopack 发布后的各方反应:Vite/Webpack/围观群众

上周整理了一下 Turbopack 发布后的各方反应,以便让自己和各位同学下一步做决策的时候能有所参考。接着忙碌的一周过去,我发现博客这周还没更,于是赶紧来补一下。

  1. TL;DR

  2. Turbopack 的宣传语更多还是“宣传”,实际效果提升并没有那么巨大。
  3. Turbopack 目前只支持 Next.js + React,且没有建立健全插件机制,所以没有生态可言,如果我们的主要开发环境不一致,可能暂时用不到。
  4. Turbopack 基于 Rust 开发,性能提升主要来自 SWC,所以有时间、有兴趣的话,直接使用 SWC 替换编译工具就好。
  5. 非目标开发者群体建议先不考虑迁移。
  6. Turbopack 对于打包过程有不小的改进,值得持续关注。
  7. 如果你日常使用 React,且想在新技术出现时斩获先机,早点动手也挺好。

2022-10-26

首先,10月26日,Vercel 发布了 Turbopack,自命 Webpack 继任者,号称速度比 Wepback 快 700 倍,比 Vite 快 10 倍。

这些是官推亮点:

  • ~700x faster than Webpack -> 比 Webpack 快 700 倍
  • 10x faster than Vite -> 比 Vite 快 10 倍
  • Native incremental architecture built with Rust -> 基于 Rust,具备原生可增长架构

    • ◆ Support for React Server Components -> 支持 React 服务器组件
    • ◆ Support for TS, JSX, CSS & more -> 支持 TS、JSX、CSS,以及更多
  • 官推:https://twitter.com/vercel/st...
  • 官网:https://vercel.com/blog/turbo...

尤大观点

很快,Vite 作者 @youyuxi 就跟进表达反驳:

“10x faster than Vite” isn’t entirely accurate

“‘比 Vite 快 10 倍’并不完全准确”,他说。

然后他进一步介绍了自己的理由:

Vite’s default React HMR is still babel-based, while Next/turbopack use swc/rust-based transform, so the HMR performance difference is a bit of apples to oranges.

因为 Vite 默认的 HMR 仍然基于 Babel,而Next/Turbopack 使用的是基于 Rust 平台的 SWC,所以这里 HMR 的性能差异是关公战秦琼(这个 thread 后面还有若干理由,我就不一一翻译了):

Vite can switch to the swc/rust transform if necessary, we currently chose not to do that because adding swc to the deps list is extra weight, and even without it HMR is fast enough.

Vite 当然可以在必要时切换到 swc/rust,但 Vite 开发团队并不想这样做,因为即使只用 Babel(不那样做),HMR 也足够快。使用 SWC 替换 Babel 只是平添开发负担。

turbopack does address Vite’s request congestion issue on large apps. On Vite side we are also exploring how we can solve this via upcoming browser features like web bundles.

Turbopack 确实解决了 Vite 在大型应用上的请求拥塞问题。我们 Vite 团队这边,也在探索如何解决这个问题,比如,是否可以借助即将推出的浏览器功能(Web Bundles)。

In the long run we may also consider using turbopack under the hood to replace esbuild/Rollup (where suitable), due to its strong caching capabilities.

从长远来看,由于 Turbopack 强大的缓存能力,我们也会考虑在底层使用 Turbopack 来替换 esbuild/Rollup。如果合适的话。

I’m glad vercel is investing its resources into a proper native-speed successor to webpack (as it should), and that our work on Vite has pushed this to happen. It’ll be interesting to see how it evolves – my hope is it can be made truly framework agnostic, not Next-first.

我很高兴 Vercel 愿意投入资源,打造 Webpack 的继任者,并着力提升它们的速度表现(应该如此),我们在 Vite 上的努力也达到了这样的目的。我对它们未来的演变很感兴趣——我希望它不要和框架捆绑在一起,不要 Next-first。

Sean Larkin 观点

(Sean Larkin 是 Webpack 的核心团队成员。目前好像在 LinkedIn 工作。)

Sean Larkin 也表达了自己的观点:

Few thoughts: 几点想法:

  1. Love the innovation but it looks moderately SWC powers and wish there was some more callout there.
  2. Disappointed how entangled this is with next/turborepo. But Vercel needs to make.
  3. The path of migration from webpack for the avg user will be hard.
  4. I like that the dev server is still bundling modules because ESM slower than raw ESM. I need to peel back more layers to try and get a standalone implementation working.
  5. It’s all in alpha so we need to still look at the broader view but I hope we’re selling less going fwd
  1. 我喜欢创新。但看起来,Turbopack 更多是借力于 SWC,希望对方能表达得清楚一些。
  2. 我对 Turborepo 与 Next 的绑定程度感到失望。但是 Vercel 需要挣到更多的 (天使资金),没办法。
  3. 普通用户想 Webpack 迁移过去,会很艰难。
  4. 我倾向于开发服务器上的模块仍然是打包后的,因为 ESM 比原始 ESM 慢。我需要剥离更多层,才能让独立的实现工作。
  5. 目前它还处于 alpha 阶段,所以我们要看开一点,但我仍然希望,销售手腕少一些,好好干开发。

2022-10-27 ~ 2022-10-28

这段时间,@youyuxi 觉得 Turbopack 的数据有问题,因为 Vercel benchmark 的设计有问题,于是他尝试调整测试方式,给出了一些数据。不过后来他删除了这些推文,因为数字不太对。

2022-10-27

Vite 核心成员之一,@antfu7 在知乎上表达了自己的观点:

他认为 Vite 更吸引人的是其插件系统,和构建在上面的生态系统。这样才能将改进快速带给其他领域。Turbopack 方面,静观其变吧。

https://zhihu.com/question/56...
如何评价Vercel开源的使用Rust实现的Turbopack? – Anthony Fu的回答 – 知乎

2022-10-29

经过几天实验,Vite 作者 @youyuxi 发表了新的推文,新推文介绍了他进一步比较 Turbopack 和 Vite 之间性能之后,得到的新结论。

I updated the vite vs next/turbo benchmark by using swc to transform React refresh for vite. To avoid confusion, I have deleted the previous two tweets with outdated numbers.

Latest numbers:

  • root: vite 338.2ms / next 334.6ms
  • leaf: vite 141.8ms / next 84.4ms

我通过搭配 Vite+SWC,重新评测 React 转换之后,更新 Vite vs Next/Turbopack 的基准测试。为避免混淆,我删除了前两条带有过时数字的推文。

最新的对比数字:

根:Vite 338.2ms / Next 334.6ms
叶:Vite 141.8ms / Next 84.4ms

The swc transform helps a lot in the root component case because the file is big, and previously cost 300ms in babel transforms alone. This makes vite almost exactly the same speed with turbopack.

SWC 转换在根组件时候提供了很大帮助。因为文件很大,以前仅在 Babel 中转换就要花费 300 毫秒。替换之后,Vite 的速度几乎与 Turbopack 完全相同。

Interestingly, in leaf scenarios turbopack is still 69% faster – this entails that Vite HMR actually caught up with turbopack as the file gets bigger, potentially scaling better in larger apps.

有趣的是,在叶子场景中,Turbopack 的速度仍然更快,提升约有 69%。——这意味着随着文件变大,Vite HMR 实际上赶上了 Turbopack,在较大的应用程序中可能会有更好的扩展性。

测试用仓库:https://github.com/yyx990803/...

Because we are now testing the HMR of the same framework with the same set of transforms, the result is more relevant for other frameworks compared to previously posted numbers.

现在,因为我们使用相同的转换集测试同一框架的 HMR,与之前发布的数字相比,结果的相关度更高。

Just to add a bit of context – in this benchmark, even before using swc for React transforms, turbo is only 2x compared to vite (compared to marketed 10x). So the difference wasn’t that drastic to begin with.

顺便讲点相关前提:这套基准测试,甚至在替换使用 SWC 转换 React 之前,与 Vite 相比,Turbo 也会仅 2 倍(他们市场宣称要快 10 倍)。所以其实一直以来,差异都没有那么大。

2022-10-31 及之后

本文主体整理于 10 月 31 日,之后 @youyuxi 还在孜孜不倦的跑 benchmark,与各路支持、反对、吃瓜者或讨论或争论。但是他的观点变化不大,所以我也就偷懒不继续翻译了,相信大家看完都能做出自己的判断。

N 神有一些观点,摘录于此:

vite 为了提升速度,利用了浏览器的特性,在 dev 阶段不打包而用 esbuild 预编译 esm 小包投送给浏览器, 在 build时候用 rollup 再打包。这样 dev 就会非常快(因为无需打包),但写插件就很分裂,要考虑 dev 和 build 两种情况。并且理论上如果依赖小包过多会肯定会遇到浏览器并发瓶颈减慢速度。

按 Turbopack 的说法,他 dev 和 build 同样走打包流程,还能比 vite 快,那么铁定是更好的。只是太早期了,现在还没有开放插件生态。而且自命 webpack 继任者没毛病,看 github 上就是 webpack 作者主力搞的。

但另一方面如果未来 non-bundler 成为主流,前端不再需要打包。turbopack就没用了。vite 抛弃 rollup,build 也走 dev 流程就更美了。

https://twitter.com/nshen121/...

SWC 作者和 Webpack 作者

这二位目前都在 Vercel 工作,可能受公司公关限制,都只转发了官推,并没有发表更多意见。

总结

Turbopack 可能不如官方所说的那么好。它的确带来了 HMR 的提升,但代价是不健全的插件机制和生态环境,以及难以被前端团队掌握的 Rust 平台。

未来一段时间,我们还要继续坚持在 Vite 平台上。

vue开发工具node.js及构建工具webpack

vue开发工具node.js及构建工具webpack

1.概念

  node.js:可以运行JavaScript的服务平台,可以把它当做一个后端程序,只是它的开发语言是JavaScript

  (通常情况下,JavaScript的运行环境都是浏览器,因此JavaScript的能力也就局限于浏览器能赋予它的权限了。比如说读写本地系统文件这种操作,一般情况下运行在浏览器中的JavaScript代码是没有这个操作权限的。如果我们想用JavaScript写出一些能够运行在操作系统上的,能够具有像PHP,JAVA之类的编程语言具有的功能的程序该怎么办呢?Node.js就解决了这个问题。Node.js是一个服务端的JavaScript运行环境,通过Node.js可以实现用JavaScript写独立程序。)

  npm:node.js的包管理器,相当于python的pip

  (传统开发的时候,JQuery.js大多都是百度搜索,然后去官网下载,或者直接引入CDN资源,这种方法太过于麻烦。如果以后遇到其他的包,这个包和其他的那几个包存在依赖关系,那么我们要在自己的项目中引入一个包将变得十分困难。现在我们有了NPM这个包管理器,直接可以通过npm install xxx包名称引入,如npm install vue,就自动在当前项目文件夹下导入了这个包,并且npm自动下载好了vue这个包依赖的其他包

  webpack:一个前端打包和构建工具

  (因为单页应用程序中用到很多素材,如果每一个素材都通过在HTML中以src属性或者link来引入,那么请求一个页面的时候,可能浏览器就要发起十多次请求,往往请求的这些资源都是一些脚本代码或者很小的图片,这些资源本身才几k,下载连1秒都不需要,但是由于HTTP是应用层协议,它的下层是TCP这个运输层协议,TCP的握手和挥手过程消耗的时间可能比下载资源本身还要长,所以需要把这些小文件全部打包成一个文件,这样只要一次TCP握手和挥手的过程,就把多个资源给下载下来了,并且多个资源由于都是共享一个HTTP请求,所以head等部分也是共享的,相当于形成了规模效应,让网页展现更快,用户体验更好。)

  vue-CLi:vue.js的脚手架工具。说白了就是一个自动帮你生成好项目目录,配置好Webpack,以及各种依赖包的工具,通过npm install vue-cli -g 安装,-g表示全局安装

 2.搭建项目

1.cmd下:
	node -v
	npm -v
	查看vue和npm是否安装成功

2.开始新建vue项目
	1.安装脚手架工具
	npm install -g vue-cli 
	2.查看版本号,查看是否安装成功
	vue --version
	3.创建框架项目(注意路径)
	vue init webpack my-project(my-project是项目名称)
	4.查看项目文件
	cd my-project(项目名)
	5.启动项目
	npm run dev

创建项目相关选择项:

2.1引入jquery和bootstrap

vue引入jquery(注意,是在你项目路径下)

npm install jquery

 选择版本号3.3.1安装:

npm install jquery@3.3.1

 main.js下引入全局jquery

import jQuery from ''jquery''

vue下引入bootstrap(注意,是在你项目路径下)

npm install bootstrap --save-dev  

 选择性安装,版本3.3.7:

npm install bootstrap@3.3.7 -d (开发版)

main.js下引入:

import ''bootstrap/dist/css/bootstrap.min.css''
import ''bootstrap/dist/js/bootstrap.min''

2.2项目框架说明  

  

 3.webpack介绍

  Webpack把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

 

总结:

1. 组件间的传值
	1. bus  --> 空Vue对象
		通过向bus对象抛出自定义事件的方式在组件间传递信息
	2. 注意事项:
		1. bus.$on()应该在组件mounted(挂载在页面上)的时候就执行
		2. this对象
2. Vue实例的生命周期钩子函数(8个)
	1. beforeCreate
		data属性光声明没有赋值的时候
	2. created
		data属性完成了赋值
	3. beforeMount
		页面上的{{name}}还没有被渲染成真正的数据
	4. mounted
		页面上的{{name}}被渲染成真正的数据
		
	5. beforeUpdate
		数据(data属性)更新之前会执行的函数
	6. updated
		数据(data属性)更新完会执行的函数
	7. beforeDestroy
		实例被销毁之前会执行的函数
	8. destroyed
		实例被销毁后会执行的函数
3. VueRouter
	在前端做路由的
	1. 基本使用
		1. 先写路由
		2. 生成路由实例
		3. 将路由实例挂载到Vue对象上
		
		4. <router-link></router-link>
		   <router-view></router-view>    <==> <router-view/>
	2. 路由的模糊匹配
		1. path: ''/user/:name''     ---> $route.params.name
		2. /user/alex?age=9000     ---> $route.query.age
	
	3. 子路由
		children
	4. 编程式导航
		用JS代码去控制页面跳转
		this.$router.push(...)
4. Vue-CLI
	一个脚手架工具,帮助我们快速的搭建Vue项目
	1. 查看本机安装的vueCLI的版本
		vue -V    ---> 2.9.6
	2. 使用Vue CLI创建Vue项目
		vue init webpack mysite
	
5. 组件中捕获原生事件
	.native修饰符

  

 

webpack 4.x 解决 webpack-dev-server工具在webpack构建的项目中使用问题

webpack 4.x 解决 webpack-dev-server工具在webpack构建的项目中使用问题

webpack-dev-server工具能实现自动打包编译和热更新

首先将webpack-dev-server安装到项目中

npm install webpack-dev-server -D

这时在命令行窗口中敲 webpack-dev-server 会发现

''webpack-dev-server'' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

这是由于该工具只是安装到本地项目中了

纳尼? 难道要全局安装一下吗???

没必要!!!这时只需在根目录的package.json"scripts"下中添加

"dev": "webpack-dev-server"

 

当然此处可以带一些参数配置:  --open      启动时打开浏览器

              --port      设置端口号

              --contentBase  设置根目录(即 设置 http://localhost:8080 打开时显示哪个界面)

              --hot      热更新

"dev": "webpack-dev-server --open --port 8080 --contentBase src --hot"

 

 

即可使用 npm run dev 来运行项目了

但是笔者在运行项目时 报了以下错误:

该问题的出现是由于webpack没有安装到项目中导致的;只需要将之安装到项目中即可:

npm install webpack -D

但是 运行依然出错:

此问题的出现 通过查阅得知 应该是 webpack 与 webpack-cli版本不一致导致的

于是:

npm uninstall webpack -D
npm install webpack webpack-cli -D 

然后 npm run dev 运行成功  然后就可以http://localhost:8080访问了

如果没有安装成功可能出现的情况是缓存没有清

npm cache clean --force

Webpack PK Vite,2021谁是下一代的前端构建工具

Webpack PK Vite,2021谁是下一代的前端构建工具

前两天在知乎看到过一篇文章,大致意思是讲:字节跳动已经开始“弃用Webpack”,尝试在自研构建工具中使用类似Vite的ESmodule构建方式

引起下方一大片焦虑:

Webpack是不是要被取代了?现在学Vite就行了吧

Webpack还没学会,就又来新的了!

甚至有人搬出了去年尤大所发的一个动态:再也回不去Webpack了

按照这种说法,是不是明年马上 Webpack 就要被取代了, Vite 的时代就要到来了呢

如今前端已经进入工程化时代,业务逻辑逐渐复杂,企业对于前端的应用功能要求不断提高,可以说从项目搭建到部署上线的每一个过程都可以通过工程化,提高工作效率

所以说,项目工程化能力是前端程序员需要掌握的重点。

Webpack、Vite作为前端热门的工程化构建工具,它们都有各自的适用场景,不存在“取代”这一说法。

初级程序员会用已有框架做程序,而中高级前端想要进阶就需要去了解更多的工具,不管是Webpack、Vite还是Rollup,都是需要掌握的内容。

特别是要进大厂,面试官免不了要问几个关于工程化的问题。

因此,给大家推荐一个由拥有12年工作经验,热爱造轮子的全栈工程师然叔主讲的视频课程《前端构建工具大PK》,带你手写Webpack、Vite以及Rollup,对比分析三大构建工具的优劣势,深度剖析底层原理,层层递进,构造你个人的前端技术护城河。

除此之外,然叔还将带你对比3份简历,并且教你如何修改P6级别适合的简历

节约试错成本,现在0.99元即可报名。

上课时间:6月25日-6月27日

立即扫码

深度剖析webpack,vite,rollup原理


0 1
适合人群

如果你也有以下苦恼
  • 一年以上前端经验不知道如何进阶为中高级前端;

  • 想要进大厂却因面试而屡屡碰壁

  • 只了解Webpack,想要了解Vite、Rollup却不知从何学起。

那你来听课准有收获。
0 2
3天课程安排

3天时间,然叔将带你手写Webpack、Vite、Rollup,分析他们的优势特点以及各自的适用场景。同时将带你对比3份简历,并且教你 如何修改P6级别适合的简历
03
主讲老师介绍

然叔拥有12年工作经验,精通各种前端技术,喜欢造各种轮子,曾经做过 711电子商务系统、中国电信手机网等大型项目, 领导过数十人的开发团队。
作为 掘金经常性霸榜作者 ,跟着然叔学习不会错!大家可以先去了解一下哦~

除了然叔,村长及大帅也是全栈课程讲师,同样经常分享技术性文章,感兴趣的朋友们可以关注一下哦

0 4
课程惊喜福利

此次课程采取了“ 预习+上课+作业 ”的形式,保障学习效果。还有资深助教在群内进行答疑,有问题随时提。
除此之外,还有视频资料、面试资料等福利等你拿!

节约试错成本,现在0.99元即可报名。

上课时间:6月25日-6月27日

立即扫码

深度剖析webpack,vite,rollup原理

注:报名后记得添加老师微信领取福利~

本课程自开通学习权限后1个月内有效,可重复回放。


本文分享自微信公众号 - 前端从进阶到入院(code_with_love)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

关于深度分析前端构建工具:Vite2 v.s Snowpack3 v.s. Webpack5前端构建工具深度剖析的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于Turbopack 发布后的各方反应:Vite/Webpack/围观群众、vue开发工具node.js及构建工具webpack、webpack 4.x 解决 webpack-dev-server工具在webpack构建的项目中使用问题、Webpack PK Vite,2021谁是下一代的前端构建工具的相关信息,请在本站寻找。

本文标签:

上一篇webpack学习总结(webpack基础知识)

下一篇import被webpack编译成了啥?(import webpack)