如何在没有 Sapper 的情况下使用 Svelte 进行代码拆分

Posted

技术标签:

【中文标题】如何在没有 Sapper 的情况下使用 Svelte 进行代码拆分【英文标题】:How to do code splitting using Svelte without Sapper 【发布时间】:2020-05-18 08:01:59 【问题描述】:

如何使用 Svelte 进行代码拆分?

(我看到你可以使用 Sapper 来做到这一点,但我不想依赖节点后端)

【问题讨论】:

【参考方案1】:

代码拆分实际上是动态导入的一个花哨名称。以下是使用 Rollup 的方法(在此过程中您还将获得惊人的 tree-shaking!)。

动态导入提醒:

// "normal" static ES import
//
// - statically analytisable
// - must be called at top level
// - will be greedily resolved (and most often inlined) by your bundler
//
import Foo from './Foo.svelte'

// dynamic import
//
// - called like a function
// - returns a promise
// - default export is accessible on key `default` of the result
// - will be bundled into its own chunk by your bundler (hence code splitting)
//
import('./Foo.svelte').then(module => 
  const cmp = module.default
  console.log(module.myNamedExport)
)

请注意,动态导入是原生 ES 功能,就像正常导入一样。这意味着它们受到非过时浏览器的原生支持。

Rollup 支持“从动态导入中拆分代码”已有一段时间了(请参阅 docs)。

因此,如果您希望在项目中进行代码拆分,主要是配置 Rollup 以便将动态导入分块(另一种选择是解析并内联它们,这不会导致代码拆分)。

以下是执行此操作的步骤,从 Svelte 的 official template 开始。

    output.format 更改为'es'output.file 更改为output.dir(例如'public/build') 更改index.html中的<script>标签指向新的入口点/build/main.js,并使用type="module" 使用动态导入编写一些代码 添加对旧版浏览器的支持

汇总配置:output.formatoutput.dir

并非汇总中可用的所有输出格式都支持动态导入。 Svelte 模板的默认设置,iife 没有,所以我们需要更改。

output.format: 'es' 不会重写代码中的 import 语句。这意味着我们将依赖浏览器的本地模块加载器。目前所有浏览器都支持 ES import 或动态 import(...),旧版浏览器可以进行 polyfill。

另一个选项可能是,例如,output.format: 'system',用于SystemJS,但这需要我们除了提供我们的代码之外还提供第三方模块加载器。

我们还需要将output.file 更改为output.dir,因为代码拆分不会产生单个bundle.js 文件,而是多个块。 (而且你不能将单独的文件写入单个文件,显然......)

所以,现在是我们汇总配置的相关部分:

  input: 'src/main.js', // not changed
  output: 
    format: 'es',
    dir: 'public/build/',
  ,

如果此时您运行yarn build(或npm run build),您会看到您的应用程序现在被拆分为`/public/build/ 目录中的多个.js 文件。

index.html

我们现在需要更改index.html 中的<script> 标记(位于`public/index.html,在Svelte 模板中)以使用它。

    <script defer type="module" src="/build/main.js"></script>

首先,我们需要将srcbundle.js(这是我们的旧output.file)更改为我们应用程序的新入口点。由于我们在 Rollup 配置中的入口点 (input) 是 src/main.js,因此我们应用的主要入口点将写入 main.js(可通过 Rollup 的 entryFileNames 选项进行配置)。

由于我们的代码现在充满了 ES import 语句(因为我们使用的是 output.format='esm'),我们还需要通过添加type="module" 属性到我们的脚本标签。

现代浏览器就是这样,您现在拥有完整的代码拆分支持!

实际拆分你的应用程序

代码拆分支持不足以获得实际的代码拆分。它只是让它成为可能。您仍然需要将动态块与应用程序的其余部分(主要)分开。

您可以通过在代码中编写动态导入来做到这一点。例如:

import('./Foo.svelte')
  .then(module => module.default)
  .then(Foo =>  /* do something with Foo */ )
  .catch(err => console.error(err))

这将导致 Rollup 创建一个 Foo-[hash].js 块(可使用 chunkFileNames 选项配置),以及可能与其他组件共享的 Foo.svelte 依赖项的另一个块。

在浏览器中,只有在代码中遇到import('./Foo.svelte') 语句时才会加载此文件(延迟加载)。

(请注意,在瀑布中,FooCmp - 一个常见的 dep - 在页面加载后很长时间内被加载,由垂直红色条指示。)

旧版浏览器

Edge(最近成为 Chrome 之前)不支持动态导入。正常的 ES 导入,是的,但是动态的 import(...) 没有。这通常就是为什么你必须为过时的浏览器添加一些 polyfill。

一种解决方案,例如 rollup-starter-code-splitting 示例,是在浏览器中使用第三方模块加载器(例如 SytemJS)。

目前可用的另一个可能更简单的解决方案是使用dimport 包。它根据主机浏览器的需要填充了对 ES 导入和动态导入的支持。

为了使用它,我们将index.html 中的&lt;script&gt; 标签替换为以下内容:

    <script defer type="module" src="https://unpkg.com/dimport?module"
        data-main="/build/main.js"></script>
    <script defer type="nomodule" src="https://unpkg.com/dimport/nomodule"
        data-main="/build/main.js"></script> 

然后瞧。完整的代码拆分。 (比你想象的要简单,不是吗?)

完整示例

这是一个 complete example 实现此答案中涵盖的所有不同位。你可能对this commit特别感兴趣。

注意!请注意,该示例位于存储库的example-code-splitting 分支上,而不是master。如果你克隆 repo,你需要检查正确的分支!

示例用法:

# install
npx degit rixo/svelte-template-hot#example-code-splitting svelte-app
cd svelte-app
yarn # or npm install

# dev
yarn dev

# build
yarn build
# serve build
yarn start

【讨论】:

最佳答案!谢谢。 @rixo 非常感谢。我正在使用您的模板进行拆分。当进行生产缓存是问题所在。你有解决方案吗:github.com/sveltejs/template/issues/39main.js被浏览器缓存。想要在 index.thml 中的 main.js 使用动态哈希。 我对 index.html 使用 1 天的 HTTP 到期值(前端/代理服务器配置),并且还使用缓存破坏策略(捆绑器配置),以便 main.js 脚本 src 每次部署都不同。【参考方案2】:

这个 repo 可能是一个很好的起点https://github.com/Rich-Harris/rollup-svelte-code-splitting

【讨论】:

以上是关于如何在没有 Sapper 的情况下使用 Svelte 进行代码拆分的主要内容,如果未能解决你的问题,请参考以下文章

我对Sapper / Svelte有一些疑问

如何将 Google Adsense 添加到 Svelte/Sapper 网络应用程序?

如何将 AWS Amplify 与 Sapper 一起使用?

如何仅在 Sapper 的客户端上导入 Firebase?

如何在 Sapper 中使用 mysql2 库?

如何在 sapper 中添加全局样式