Skip to content

Commit 5adefb9

Browse files
authored
feat: explicit async loading (#212)
1 parent 64e10cc commit 5adefb9

File tree

11 files changed

+61
-72
lines changed

11 files changed

+61
-72
lines changed

README.md

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ See [live demo](https://codesandbox.io/s/nuxt-components-cou9k) or [video exampl
7272
### Lazy Imports
7373

7474
Nuxt by default does code-splitting per page and components. But sometimes we also need to lazy load them:
75+
7576
- Component size is rather big (or has big dependencies imported) like a text-editor
7677
- Component is rendered conditionally with `v-if` or being in a modal
7778

@@ -88,18 +89,18 @@ You now can easily import a component on-demand:
8889
</template>
8990

9091
<script>
91-
export default {
92-
data () {
93-
return {
94-
foo: null
95-
}
96-
},
97-
methods: {
98-
async loadFoo () {
99-
this.foo = await this.$axios.$get('foo')
92+
export default {
93+
data() {
94+
return {
95+
foo: null
96+
}
97+
},
98+
methods: {
99+
async loadFoo() {
100+
this.foo = await this.$axios.$get('foo')
101+
}
100102
}
101103
}
102-
}
103104
</script>
104105
```
105106

@@ -127,10 +128,7 @@ For clarity, it is recommended that component file name matches its name. You ca
127128
If for any reason different prefix is desired, we can add specific directory with the `prefix` option: (See [directories](#directories) section)
128129

129130
```js
130-
components: [
131-
'~/components/',
132-
{ path: '~/components/foo/', prefix: 'foo' }
133-
]
131+
components: ['~/components/', { path: '~/components/foo/', prefix: 'foo' }]
134132
```
135133

136134
## Overwriting Components
@@ -169,7 +167,7 @@ export default {
169167
components: [
170168
'~/components', // shortcut to { path: '~/components' }
171169
{ path: '~/components/awesome/', prefix: 'awesome' }
172-
],
170+
]
173171
}
174172
```
175173

@@ -210,23 +208,21 @@ If you prefer to split your SFCs into `.js`, `.vue` and `.css`, you can only ena
210208
```js
211209
// nuxt.config.js
212210
export default {
213-
components: [
214-
{ path: '~/components', extensions: ['vue'] }
215-
]
211+
components: [{ path: '~/components', extensions: ['vue'] }]
216212
}
217213
```
218214

219215
#### pattern
220216

221-
- Type: `string` ([glob pattern]( https://github.com/isaacs/node-glob#glob-primer))
217+
- Type: `string` ([glob pattern](https://github.com/isaacs/node-glob#glob-primer))
222218
- Default: `**/*.${extensions.join(',')}`
223219

224220
Accept Pattern that will be run against specified `path`.
225221

226222
#### ignore
227223

228224
- Type: `Array`
229-
- Items: `string` ([glob pattern]( https://github.com/isaacs/node-glob#glob-primer))
225+
- Items: `string` ([glob pattern](https://github.com/isaacs/node-glob#glob-primer))
230226
- Default: `[]`
231227

232228
Ignore patterns that will be run against specified `path`.
@@ -244,8 +240,8 @@ Example below adds `awesome-`/`Awesome` prefix to the name of components in `awe
244240
// nuxt.config.js
245241
export default {
246242
components: [
247-
'~/components',
248-
{ path: '~/components/awesome/', prefix: 'awesome' }
243+
'~/components',
244+
{ path: '~/components/awesome/', prefix: 'awesome' }
249245
]
250246
}
251247
```
@@ -261,7 +257,7 @@ components/
261257
<template>
262258
<div>
263259
<AwesomeButton>Click on me 🤘</AwesomeButton>
264-
<Button>Click on me</Button>
260+
<button>Click on me</button>
265261
</div>
266262
</template>
267263
```
@@ -298,7 +294,7 @@ Level are use to define a hint when overwriting the components which have the sa
298294
export default {
299295
components: [
300296
'~/components', // default level is 0
301-
{ path: 'my-theme/components', level: 1 }
297+
{ path: 'my-theme/components', level: 1 }
302298
]
303299
}
304300
```
@@ -314,9 +310,7 @@ These properties are used in production to configure how [components with `Lazy`
314310

315311
```js
316312
export default {
317-
components: [
318-
{ path: 'my-theme/components', prefetch: true }
319-
]
313+
components: [{ path: 'my-theme/components', prefetch: true }]
320314
}
321315
```
322316

@@ -330,16 +324,23 @@ const componets = {
330324
}
331325
```
332326

327+
#### isAsync
328+
329+
- Type: Boolean
330+
- Default: `false` unless component name ends with `.async.vue`
331+
332+
This flag indicates, component should be loaded async (with a seperate chunk) regardless of using `Lazy` prefix or not.
333+
333334
## Migration guide
334335

335336
## `v1` to `v2`
336337

337338
Starting with `[email protected]`, Nuxt uses `@nuxt/components` v2:
338339

339340
- All components are globally available so you can move `components/global/`
340-
to `components/` and `global: true` is not required anymore
341+
to `components/` and `global: true` is not required anymore
341342
- Full path inside `components` is used to prefix component names. If you were structing your
342-
components in multiple directories, should either add prefix or register in `components` section of `nuxt.config` or use new `pathPrefix` option.
343+
components in multiple directories, should either add prefix or register in `components` section of `nuxt.config` or use new `pathPrefix` option.
343344

344345
**Example:**
345346

@@ -363,7 +364,7 @@ export default {
363364
'~/components/templates',
364365
'~/components/atoms',
365366
'~/components/molecules',
366-
'~/components/organisms',
367+
'~/components/organisms'
367368
]
368369
}
369370
```
@@ -393,8 +394,8 @@ Then in `awesome-ui/nuxt.js` you can use the `components:dir` hook:
393394
```js
394395
import { join } from 'path'
395396

396-
export default function () {
397-
this.nuxt.hook('components:dirs', (dirs) => {
397+
export default function() {
398+
this.nuxt.hook('components:dirs', dirs => {
398399
// Add ./components dir to the list
399400
dirs.push({
400401
path: join(__dirname, 'components'),
@@ -408,10 +409,7 @@ That's it! Now in your project, you can import your ui library as a Nuxt module
408409

409410
```js
410411
export default {
411-
buildModules: [
412-
'@nuxt/components',
413-
'awesome-ui/nuxt'
414-
]
412+
buildModules: ['@nuxt/components', 'awesome-ui/nuxt']
415413
}
416414
```
417415

@@ -438,15 +436,11 @@ Next: publish your `awesome-ui` module to [npm](https://www.npmjs.com) and share
438436

439437
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/components/latest.svg?style=flat-square
440438
[npm-version-href]: https://npmjs.com/package/@nuxt/components
441-
442439
[npm-downloads-src]: https://img.shields.io/npm/dt/@nuxt/components.svg?style=flat-square
443440
[npm-downloads-href]: https://npmjs.com/package/@nuxt/components
444-
445441
[github-actions-ci-src]: https://img.shields.io/github/workflow/status/nuxt/typescript/test?label=ci&style=flat-square
446442
[github-actions-ci-href]: https://github.com/nuxt/components/actions?query=workflow%3Aci
447-
448443
[codecov-src]: https://img.shields.io/codecov/c/github/nuxt/components.svg?style=flat-square
449444
[codecov-href]: https://codecov.io/gh/nuxt/components
450-
451445
[license-src]: https://img.shields.io/npm/l/@nuxt/components.svg?style=flat-square
452446
[license-href]: https://npmjs.com/package/@nuxt/components

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
],
2020
"scripts": {
2121
"build": "siroc build",
22-
"dev": "nuxt-ts test/fixture",
22+
"dev": "nuxt dev test/fixture",
2323
"lint": "eslint --ext .ts,.js,.vue .",
2424
"prepare": "yarn link && yarn link @nuxt/components",
2525
"prepublishOnly": "yarn build",

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const componentsModule: Module<Options> = function () {
7474
path: dirPath,
7575
extensions,
7676
pattern: dirOptions.pattern || `**/*.{${extensions.join(',')},}`,
77+
isAsync: dirOptions.isAsync ?? !nuxt.options.dev /* async only for prod by default */,
7778
// TODO: keep test/unit/utils.ts updated
7879
ignore: [
7980
'**/*.stories.{js,ts,jsx,tsx}', // ignore storybook files

src/loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { matcher } from './scan'
44
import type { Component } from './types'
55

66
function install (this: WebpackLoader.LoaderContext, content: string, components: Component[]) {
7-
const imports = '{' + components.map(c => `${c.pascalName}: ${c.import}`).join(',') + '}'
7+
const imports = '{' + components.map(c => `${c.pascalName}: ${c.isAsync ? c.asyncImport : c.import}`).join(',') + '}'
88

99
let newContent = '/* nuxt-component-imports */\n'
1010
newContent += `installComponents(component, ${imports})\n`

src/scan.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
1717
const filePaths = new Set<string>()
1818
const scannedPaths: string[] = []
1919

20-
for (const { path, pattern, ignore = [], prefix, extendComponent, pathPrefix, level, prefetch = false, preload = false } of dirs.sort(sortDirsByPathLength)) {
20+
for (const { path, pattern, ignore = [], prefix, extendComponent, pathPrefix, level, prefetch = false, preload = false, isAsync: dirIsAsync } of dirs.sort(sortDirsByPathLength)) {
2121
const resolvedNames = new Map<string, string>()
2222

2323
for (const _file of await globby(pattern!, { cwd: path, ignore })) {
@@ -39,6 +39,8 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
3939
if (fileName.toLowerCase() === 'index') {
4040
fileName = pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
4141
}
42+
const isAsync = fileName.endsWith('.async') ? true : dirIsAsync
43+
fileName = fileName.replace(/\.async$/, '')
4244
const fileNameParts = splitByCase(fileName)
4345

4446
const componentNameParts: string[] = []
@@ -66,12 +68,13 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
6668
const shortPath = relative(srcDir, filePath)
6769
const chunkName = 'components/' + kebabName
6870

69-
let component = {
71+
let component: Component = {
7072
filePath,
7173
pascalName,
7274
kebabName,
7375
chunkName,
7476
shortPath,
77+
isAsync,
7578
import: '',
7679
asyncImport: '',
7780
export: 'default',

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface Component {
66
export: string
77
filePath: string
88
shortPath: string
9-
async?: boolean
9+
isAsync?: boolean
1010
chunkName: string
1111
/** @deprecated */
1212
global: boolean
@@ -20,6 +20,7 @@ export interface ScanDir {
2020
pattern?: string | string[]
2121
ignore?: string[]
2222
prefix?: string
23+
isAsync?: boolean
2324
/** @deprecated */
2425
global?: boolean | 'dev'
2526
pathPrefix?: boolean

templates/components/index.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { wrapFunctional } from './utils'
22

33
<%= options.getComponents().map(c => {
4-
const exp = c.pascalName === c.export ? c.pascalName : `${c.export} as ${c.pascalName}`
5-
return `export { ${exp} } from '../${relativeToBuild(c.filePath)}'`
6-
}).join('\n') %>
7-
8-
<%= options.getComponents().map(c => {
9-
const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']`
104
const magicComments = [
115
`webpackChunkName: "${c.chunkName}"`,
126
c.prefetch === true || typeof c.prefetch === 'number' ? `webpackPrefetch: ${c.prefetch}` : false,
137
c.preload === true || typeof c.preload === 'number' ? `webpackPreload: ${c.preload}` : false,
148
].filter(Boolean).join(', ')
15-
16-
return `export const Lazy${c.pascalName} = import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))`
9+
if (c.isAsync) {
10+
const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']`
11+
const asyncImport = `import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))`
12+
return `export const ${c.pascalName} = ${asyncImport}`
13+
} else {
14+
const exp = c.export === 'default' ? `default as ${c.pascalName}` : c.pascalName
15+
return `export { ${exp} } from '../${relativeToBuild(c.filePath)}'`
16+
}
1717
}).join('\n') %>

templates/components/plugin.js

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,7 @@
11
import Vue from 'vue'
2-
import { wrapFunctional } from './utils'
3-
4-
<% const components = options.getComponents() %>
5-
6-
const components = {
7-
<%= components.map(c => {
8-
const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']`
9-
const magicComments = [
10-
`webpackChunkName: "${c.chunkName}"`,
11-
c.prefetch === true || typeof c.prefetch === 'number' ? `webpackPrefetch: ${c.prefetch}` : false,
12-
c.preload === true || typeof c.preload === 'number' ? `webpackPreload: ${c.preload}` : false,
13-
].filter(Boolean).join(', ')
14-
15-
return ` ${c.pascalName.replace(/^Lazy/, '')}: () => import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))`
16-
}).join(',\n') %>
17-
}
2+
import * as components from './index'
183

194
for (const name in components) {
205
Vue.component(name, components[name])
21-
Vue.component('Lazy' + name, components[name])
6+
Vue.component('Lazy' + name, () => Promise.resolve(components[name]))
227
}

templates/components/readme_md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const components = options.getComponents()
1111
const list = components.map(c => {
1212
const pascalName = c.pascalName.replace(/^Lazy/, '')
1313
const kebabName = c.kebabName.replace(/^lazy-/, '')
14-
return `- \`<${pascalName}>\` | \`<${kebabName}>\` (${c.shortPath})`
14+
const tags = c.isAsync ? ' [async]' : ''
15+
return `- \`<${pascalName}>\` | \`<${kebabName}>\` (${c.shortPath})${tags}`
1516
})
1617
%><%= list.join('\n') %>

0 commit comments

Comments
 (0)