Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: explicit async loading #212

Merged
merged 12 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 34 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ See [live demo](https://codesandbox.io/s/nuxt-components-cou9k) or [video exampl
### Lazy Imports

Nuxt by default does code-splitting per page and components. But sometimes we also need to lazy load them:

- Component size is rather big (or has big dependencies imported) like a text-editor
- Component is rendered conditionally with `v-if` or being in a modal

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

<script>
export default {
data () {
return {
foo: null
}
},
methods: {
async loadFoo () {
this.foo = await this.$axios.$get('foo')
export default {
data() {
return {
foo: null
}
},
methods: {
async loadFoo() {
this.foo = await this.$axios.$get('foo')
}
}
}
}
</script>
```

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

```js
components: [
'~/components/',
{ path: '~/components/foo/', prefix: 'foo' }
]
components: ['~/components/', { path: '~/components/foo/', prefix: 'foo' }]
```

## Overwriting Components
Expand Down Expand Up @@ -169,7 +167,7 @@ export default {
components: [
'~/components', // shortcut to { path: '~/components' }
{ path: '~/components/awesome/', prefix: 'awesome' }
],
]
}
```

Expand Down Expand Up @@ -210,23 +208,21 @@ If you prefer to split your SFCs into `.js`, `.vue` and `.css`, you can only ena
```js
// nuxt.config.js
export default {
components: [
{ path: '~/components', extensions: ['vue'] }
]
components: [{ path: '~/components', extensions: ['vue'] }]
}
```

#### pattern

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

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

#### ignore

- Type: `Array`
- Items: `string` ([glob pattern]( https://github.com/isaacs/node-glob#glob-primer))
- Items: `string` ([glob pattern](https://github.com/isaacs/node-glob#glob-primer))
- Default: `[]`

Ignore patterns that will be run against specified `path`.
Expand All @@ -244,8 +240,8 @@ Example below adds `awesome-`/`Awesome` prefix to the name of components in `awe
// nuxt.config.js
export default {
components: [
'~/components',
{ path: '~/components/awesome/', prefix: 'awesome' }
'~/components',
{ path: '~/components/awesome/', prefix: 'awesome' }
]
}
```
Expand All @@ -261,7 +257,7 @@ components/
<template>
<div>
<AwesomeButton>Click on me 🤘</AwesomeButton>
<Button>Click on me</Button>
<button>Click on me</button>
</div>
</template>
```
Expand Down Expand Up @@ -298,7 +294,7 @@ Level are use to define a hint when overwriting the components which have the sa
export default {
components: [
'~/components', // default level is 0
{ path: 'my-theme/components', level: 1 }
{ path: 'my-theme/components', level: 1 }
]
}
```
Expand All @@ -314,9 +310,7 @@ These properties are used in production to configure how [components with `Lazy`

```js
export default {
components: [
{ path: 'my-theme/components', prefetch: true }
]
components: [{ path: 'my-theme/components', prefetch: true }]
}
```

Expand All @@ -330,16 +324,23 @@ const componets = {
}
```

#### isAsync

- Type: Boolean
- Default: `false` unless component name ends with `.async.vue`

This flag indicates, component should be loaded async (with a seperate chunk) regardless of using `Lazy` prefix or not.

## Migration guide

## `v1` to `v2`

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

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

**Example:**

Expand All @@ -363,7 +364,7 @@ export default {
'~/components/templates',
'~/components/atoms',
'~/components/molecules',
'~/components/organisms',
'~/components/organisms'
]
}
```
Expand Down Expand Up @@ -393,8 +394,8 @@ Then in `awesome-ui/nuxt.js` you can use the `components:dir` hook:
```js
import { join } from 'path'

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

```js
export default {
buildModules: [
'@nuxt/components',
'awesome-ui/nuxt'
]
buildModules: ['@nuxt/components', 'awesome-ui/nuxt']
}
```

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

[npm-version-src]: https://img.shields.io/npm/v/@nuxt/components/latest.svg?style=flat-square
[npm-version-href]: https://npmjs.com/package/@nuxt/components

[npm-downloads-src]: https://img.shields.io/npm/dt/@nuxt/components.svg?style=flat-square
[npm-downloads-href]: https://npmjs.com/package/@nuxt/components

[github-actions-ci-src]: https://img.shields.io/github/workflow/status/nuxt/typescript/test?label=ci&style=flat-square
[github-actions-ci-href]: https://github.com/nuxt/components/actions?query=workflow%3Aci

[codecov-src]: https://img.shields.io/codecov/c/github/nuxt/components.svg?style=flat-square
[codecov-href]: https://codecov.io/gh/nuxt/components

[license-src]: https://img.shields.io/npm/l/@nuxt/components.svg?style=flat-square
[license-href]: https://npmjs.com/package/@nuxt/components
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
],
"scripts": {
"build": "siroc build",
"dev": "nuxt-ts test/fixture",
"dev": "nuxt dev test/fixture",
"lint": "eslint --ext .ts,.js,.vue .",
"prepare": "yarn link && yarn link @nuxt/components",
"prepublishOnly": "yarn build",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const componentsModule: Module<Options> = function () {
path: dirPath,
extensions,
pattern: dirOptions.pattern || `**/*.{${extensions.join(',')},}`,
isAsync: dirOptions.isAsync ?? !nuxt.options.dev /* async only for prod by default */,
// TODO: keep test/unit/utils.ts updated
ignore: [
'**/*.stories.{js,ts,jsx,tsx}', // ignore storybook files
Expand Down
2 changes: 1 addition & 1 deletion src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { matcher } from './scan'
import type { Component } from './types'

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

let newContent = '/* nuxt-component-imports */\n'
newContent += `installComponents(component, ${imports})\n`
Expand Down
7 changes: 5 additions & 2 deletions src/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
const filePaths = new Set<string>()
const scannedPaths: string[] = []

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

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

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

let component = {
let component: Component = {
filePath,
pascalName,
kebabName,
chunkName,
shortPath,
isAsync,
import: '',
asyncImport: '',
export: 'default',
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface Component {
export: string
filePath: string
shortPath: string
async?: boolean
isAsync?: boolean
chunkName: string
/** @deprecated */
global: boolean
Expand All @@ -20,6 +20,7 @@ export interface ScanDir {
pattern?: string | string[]
ignore?: string[]
prefix?: string
isAsync?: boolean
/** @deprecated */
global?: boolean | 'dev'
pathPrefix?: boolean
Expand Down
16 changes: 8 additions & 8 deletions templates/components/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { wrapFunctional } from './utils'

<%= options.getComponents().map(c => {
const exp = c.pascalName === c.export ? c.pascalName : `${c.export} as ${c.pascalName}`
return `export { ${exp} } from '../${relativeToBuild(c.filePath)}'`
}).join('\n') %>

<%= options.getComponents().map(c => {
const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']`
const magicComments = [
`webpackChunkName: "${c.chunkName}"`,
c.prefetch === true || typeof c.prefetch === 'number' ? `webpackPrefetch: ${c.prefetch}` : false,
c.preload === true || typeof c.preload === 'number' ? `webpackPreload: ${c.preload}` : false,
].filter(Boolean).join(', ')

return `export const Lazy${c.pascalName} = import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))`
if (c.isAsync) {
const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']`
const asyncImport = `import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))`
return `export const ${c.pascalName} = ${asyncImport}`
} else {
const exp = c.export === 'default' ? `default as ${c.pascalName}` : c.pascalName
return `export { ${exp} } from '../${relativeToBuild(c.filePath)}'`
}
}).join('\n') %>
19 changes: 2 additions & 17 deletions templates/components/plugin.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
import Vue from 'vue'
import { wrapFunctional } from './utils'

<% const components = options.getComponents() %>

const components = {
<%= components.map(c => {
const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']`
const magicComments = [
`webpackChunkName: "${c.chunkName}"`,
c.prefetch === true || typeof c.prefetch === 'number' ? `webpackPrefetch: ${c.prefetch}` : false,
c.preload === true || typeof c.preload === 'number' ? `webpackPreload: ${c.preload}` : false,
].filter(Boolean).join(', ')

return ` ${c.pascalName.replace(/^Lazy/, '')}: () => import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))`
}).join(',\n') %>
}
import * as components from './index'

for (const name in components) {
Vue.component(name, components[name])
Vue.component('Lazy' + name, components[name])
Vue.component('Lazy' + name, () => Promise.resolve(components[name]))
}
3 changes: 2 additions & 1 deletion templates/components/readme_md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const components = options.getComponents()
const list = components.map(c => {
const pascalName = c.pascalName.replace(/^Lazy/, '')
const kebabName = c.kebabName.replace(/^lazy-/, '')
return `- \`<${pascalName}>\` | \`<${kebabName}>\` (${c.shortPath})`
const tags = c.isAsync ? ' [async]' : ''
return `- \`<${pascalName}>\` | \`<${kebabName}>\` (${c.shortPath})${tags}`
})
%><%= list.join('\n') %>
6 changes: 5 additions & 1 deletion test/fixture/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ const config: NuxtConfig = {
nuxtComponents
],

typescript: {
typeCheck: false
},

components: [
'~/components',
{ path: '~/components/global', global: true },
{ path: '~/components/global', global: true, isAsync: false },
{ path: '~/components/no-prefix', pathPrefix: false },
{ path: '~/components/multifile', extensions: ['vue'] },
'~/non-existent',
Expand Down