当你的网站/应用规模较小时,你可以将所有布局/页面/组件加载到初始包中,并在启动时提供所有内容。但当你的代码变得复杂并且包含许多布局/页面/组件时,这样做并非最佳选择,因为它会极大地影响加载时间。幸运的是,有一种方法可以解决这个问题。
¥When your website/app is small, you can load all layouts/pages/components into the initial bundle and serve everything at startup. But when your code gets complex and has many layouts/pages/components, it won’t be optimal to do this as it will massively impact loading time. Fortunately, there is a way to solve this.
我们将介绍如何延迟加载/代码拆分应用的各个部分,以便仅在需要时自动请求它们。这是通过动态导入完成的。让我们从一个例子开始,然后将其转换为使用延迟加载 - 我们将重点介绍页面加载,但相同的原理可以应用于加载任何内容(资源、JSON 等)。
¥We’ll cover how you can lazy load / code split parts of your app so that they are automatically requested only on demand. This is done through dynamic imports. Let’s start with an example and then convert it so that we use lazy loading – we’ll focus this example on loading a page, but the same principle can be applied to load anything (assets, JSONs, …).
延迟加载路由页面(Lazy-load router pages)
¥Lazy-load router pages
使用 Vue Router 调用静态组件是正常的,如下所示。
¥It’s normal to use the Vue Router calling static components as below.
警告
Quasar 文档假设你已经熟悉 Vue 路由。下面仅描述了如何在 Quasar CLI 项目中使用它的基本方法。有关其功能的完整列表,请访问 Vue 路由文档。
¥Quasar documentation assumes you are already familiar with Vue Router. Below it’s described only the basics of how to make use of it in a Quasar CLI project. For the full list of its features please visit the Vue Router documentation.
import SomePage from 'pages/SomePage'
const routes = [
{
path: '/some-page',
component: SomePage
}
]
现在让我们修改一下,使用动态导入,使页面仅按需加载:
¥Now let’s change this and make the page be loaded on demand only, using dynamic imports:
const routes = [
{
path: '/some-page',
component: () => import('pages/SomePage')
}
]
很简单,对吧?它的作用是,它会为 /src/pages/SomePage.vue
创建一个单独的块,然后仅在需要时加载。在这种情况下,当用户访问 ‘/some-page’ 路由时。
¥Easy, right? What this does is that it creates a separate chunk for /src/pages/SomePage.vue
which is then loaded only when it is needed. In this case, when a user visits the ‘/some-page’ route.
延迟加载组件(Lazy-load components)
¥Lazy-load components
通常情况下,你需要导入一个组件,然后将其注册到页面、布局或组件。
¥Normally you would import a component and then register it to the Page, Layout or Component.
<script>
import SomeComponent from 'components/SomeComponent'
export default {
components: {
SomeComponent,
}
}
</script>
现在让我们修改一下,使用动态导入,使组件仅按需加载:
¥Now let’s change this and make the component be loaded on demand only, using dynamic imports:
<script>
import { defineAsyncComponent } from 'vue'
export default {
components: {
SomeComponent: defineAsyncComponent(() => import('components/SomeComponent')),
}
}
</script>
动态延迟加载(Lazy-load on the fly)
¥Lazy-load on the fly
正如你在上面注意到的,我们使用动态导入(import('..resource..')
)而不是常规导入(import Resource from './path/to/resource'
)。动态导入本质上是返回一个你可以使用的 Promise:
¥As you noticed above, we’re using dynamic imports (import('..resource..')
) instead of regular imports (import Resource from './path/to/resource'
). Dynamic imports are essentially returning a Promise that you can use:
import('./categories.json')
.then(categories => {
// hey, we have lazy loaded the file
// and we have its content in "categories"
})
.catch(() => {
// oops, something went wrong...
// couldn't load the resource
})
与常规导入相比,使用动态导入的一个优点是可以在运行时确定导入路径:
¥One advantage of using dynamic imports as opposed to regular imports is that the import path can be determined at runtime:
import('pages/' + pageName + '/' + id)
供应商导入注意事项(Caveat with vendor imports)
¥Caveat with vendor imports
默认情况下,即使你的代码动态导入了 node_modules
中的包,Quasar 也会将其包含在 vendor chunk 中。这会增加 vendor 块的大小,但由于你通常不会更改依赖,浏览器将使用此块的缓存版本,从而加快后续访问时应用的加载速度。
¥By default, Quasar includes packages from node_modules
in a vendor chunk even if your code imports them dynamically. This increases the vendor chunk size, but since you don’t usually change your dependencies, browsers will use the cached version of this chunk and actually speed up loading your app on subsequent visits.
例如,如果你安装了一个软件包(我们称之为 my-package
),你可以像这样动态导入它:
¥For example, if you have installed a package (let’s call it my-package
) you can import it dynamically like this:
import('my-package')
.then(myPackage => {
// use the package
})
但是,如果你想让 Quasar CLI 将 my-package
放入其自己的块中,则必须编辑 /quasar.config
文件:
¥However, should you want to make Quasar CLI put my-package
in its own chunk you’ll have to edit the /quasar.config
file:
return {
vendor: {
remove: [ 'my-package' ]
}
}
更多详情,请参阅 /quasar.config
文件的 供应商部分。
¥For more details, see the vendors section of the /quasar.config
file.
动态导入注意事项(Caveat for dynamic imports)
¥Caveat for dynamic imports
像上例一样,使用包含变量部分的动态导入时需要注意一点。当网站/应用已打包时,在编译时,我们无法确定运行时的确切导入路径。因此,将为每个与变量路径匹配的文件创建块。你可能会在构建日志中看到不必要的文件。
¥There’s one caveat when using dynamic imports with variable parts like in the previous example. When the website/app is bundled, so at compile time, we have no way of telling what the exact import path will be at runtime. As a result, chunks will be created for each file that could match the variable path. You might see un-necessary files in the build log.
那么,在这种情况下,我们如何限制创建的块数量呢?我们的想法是尽可能地限制变量部分,从而减少匹配的路径。
¥So how can we limit the number of chunks created in this case? The idea is to limit the variable part as much as you can so the matched paths are as few as possible.
添加文件扩展名,即使没有扩展名也可以正常工作。这将仅为该文件类型创建块。当文件夹包含多种文件类型时很有用。
¥Add file extension, even if it works without it too. This will create chunks only for that file types. Useful when that folder contains many file types.
// bad
import('./folder/' + pageName)
// much better
import('./folder/' + pageName + '.vue')
尝试创建一个文件夹结构,以限制该变量路径中可用的文件。尽可能具体:
¥Try to create a folder structure that will limit the files available in that variable path. Make it as specific as possible:
// bad -- makes chunks for any JSON inside ./folder (recursive search)
const asset = 'my/jsons/categories.json'
import('./folder/' + asset)
// good -- makes chunks only for JSONs inside ./folder/my/jsons
const asset = 'categories.json'
import('./folder/my/jsons/' + asset)
尝试从仅包含文件的文件夹导入。以上例为例,假设 ./folder/my/jsons 还包含子文件夹。我们通过指定更具体的路径改进了动态导入,但在本例中仍然不是最佳选择。最佳选择是使用仅包含文件的终端文件夹,因此我们限制了匹配路径的数量。
¥Try to import from folders containing only files. Take the previous example and imagine ./folder/my/jsons further contains sub-folders. We made the dynamic import better by specifying a more specific path, but it’s still not optimal in this case. Best is to use terminal folders that only contain files, so we limit the number of matched paths.
使用 Webpack 魔法注释、
webpackInclude
和webpackExclude
来通过正则表达式约束打包的块,例如:¥Use Webpack magic comments
webpackInclude
andwebpackExclude
to constrain the bundled chunks with a regular expression, for example:
await import(
/* webpackInclude: /(ar|en-US|ro)\.js$/ */
'quasar/lang/' + langIso
)
.then(lang => {
Lang.set(lang.default)
})
这将导致仅打包网站/应用所需的语言包,而不是打包所有语言包(超过 40 个!),这可能会影响命令 quasar dev
和 quasar build
的性能。
¥will result in bundling only the language packs you need for your site/app, instead of bundling all the language packs (more than 40!) which might hamper the performance of the commands quasar dev
and quasar build
.
请记住,匹配路径的数量等于生成的块的数量。
¥Remember that the number of matched paths equals to the number of chunks being generated.