feat: module utils and improvements (#38)

This commit is contained in:
pooya parsa 2021-04-02 13:47:01 +02:00 committed by GitHub
parent 1b9edfafb9
commit b3f3dc94f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 885 additions and 946 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ package-lock.json
dist dist
.nuxt* .nuxt*
.output .output
.gen
# Junit reports # Junit reports
reports reports

View File

@ -7,12 +7,13 @@
], ],
"scripts": { "scripts": {
"link": "lerna link", "link": "lerna link",
"build": "yarn workspaces run build --silent", "build": "yarn -s workspaces run build",
"stub": "yarn workspaces run stub --silent", "stub": "yarn -s build --stub",
"play": "yarn nu dev playground", "gentypes": "yarn -s --cwd packages/kit gentypes",
"lint": "eslint --ext .vue,.ts,.js .", "play": "yarn -s nu dev playground",
"test": "yarn lint", "lint": "yarn -s gentypes && eslint --ext .vue,.ts,.js .",
"postinstall": "yarn stub" "test": "yarn -s lint",
"postinstall": "yarn -s stub"
}, },
"devDependencies": { "devDependencies": {
"@nuxtjs/eslint-config": "^6.0.0", "@nuxtjs/eslint-config": "^6.0.0",
@ -26,10 +27,11 @@
"defu": "^3.2.2", "defu": "^3.2.2",
"esbuild": "^0.10.2", "esbuild": "^0.10.2",
"eslint": "^7.23.0", "eslint": "^7.23.0",
"execa": "^5.0.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"jiti": "^1.6.4", "jiti": "^1.6.4",
"lerna": "^4.0.0", "lerna": "^4.0.0",
"mkdist": "^0.1.2", "mkdist": "^0.1.3",
"pretty-bytes": "^5.6.0", "pretty-bytes": "^5.6.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^2.43.1", "rollup": "^2.43.1",

View File

@ -3,29 +3,22 @@
"version": "0.0.0", "version": "0.0.0",
"repository": "nuxt/framework", "repository": "nuxt/framework",
"license": "MIT", "license": "MIT",
"main": "./dist/index.ts", "main": "./dist/index.js",
"files": [ "files": [
"dist" "dist"
], ],
"scripts": { "scripts": {
"build": "mkdist", "build": "jiti ../../scripts/build .",
"stub": "rm -rf dist && ln -s src dist #",
"prepublishOnly": "yarn build" "prepublishOnly": "yarn build"
}, },
"dependencies": { "dependencies": {
"create-require": "^1.1.1", "hookable": "^4.4.1",
"defu": "^3.2.2", "vue": "^3.0.10"
"dotenv": "^8.2.0",
"jiti": "^1.6.4",
"rc9": "^1.2.0",
"scule": "^0.1.1",
"std-env": "^2.3.0",
"ufo": "^0.6.10",
"untyped": "^0.2.2"
}, },
"build": { "build": {
"entries": { "entries": {
"index": {} "index": { "distDir": ".gen" },
"/": {}
} }
} }
} }

View File

@ -16,7 +16,7 @@ async function initApp () {
await app.$nuxt.hooks.callHook('app:created', app) await app.$nuxt.hooks.callHook('app:created', app)
await app.$nuxt.hooks.callHook('app:beforeMount', app) await app.$nuxt.hooks.callHook('app:beforeMount', app)
app.mount('#<%= globals.id %>') app.mount('#__nuxt')
await app.$nuxt.hooks.callHook('app:mounted', app) await app.$nuxt.hooks.callHook('app:mounted', app)
await nextTick() await nextTick()

View File

@ -1,10 +0,0 @@
declare module NodeJS {
interface Process {
browser: boolean
client: boolean
mode: 'spa' | 'universal'
modern: boolean
server: boolean
static: boolean
}
}

View File

@ -1,6 +0,0 @@
declare module 'nuxt/build/routes' {
import { RouteRecordRaw } from 'vue-router'
const _default: RouteRecordRaw[]
export default _default
}

View File

@ -1,5 +0,0 @@
declare module '*.vue' {
import { Component } from 'vue'
const component: Component
export default component
}

View File

@ -1,7 +0,0 @@
import { Nuxt } from '@nuxt/app'
declare module 'vue' {
interface App {
$nuxt: Nuxt
}
}

View File

@ -1,3 +0,0 @@
interface Window {
__NUXT__?: Record<string, any>
}

View File

@ -0,0 +1 @@
export * from './index.ts'

View File

@ -1,2 +1,4 @@
export { useNuxt } from './nuxt/composables' export { useNuxt } from './nuxt/composables'
export * from './nuxt' export * from './nuxt'
export * from './shim'

View File

@ -1,5 +1,5 @@
import { getCurrentInstance } from 'vue' import { getCurrentInstance } from 'vue'
import type { Nuxt } from '@nuxt/app' import type { Nuxt } from '../nuxt'
let currentNuxtInstance: Nuxt | null let currentNuxtInstance: Nuxt | null

24
packages/app/src/shim.ts Normal file
View File

@ -0,0 +1,24 @@
import type { Nuxt } from './nuxt'
declare module 'vue' {
interface App {
$nuxt: Nuxt
}
}
declare global {
interface Window {
__NUXT__?: Record<string, any>
}
namespace NodeJS {
interface Process {
browser: boolean
client: boolean
mode: 'spa' | 'universal'
modern: boolean
server: boolean
static: boolean
}
}
}

View File

@ -10,24 +10,26 @@
], ],
"scripts": { "scripts": {
"build": "jiti ../../scripts/build .", "build": "jiti ../../scripts/build .",
"genconfig": "jiti ./scripts/genconfig", "gentypes": "jiti ./scripts/gentypes",
"stub": "yarn build --stub", "prepublishOnly": "yarn build && yarn gentypes"
"prepublishOnly": "yarn build && yarn genconfig"
}, },
"dependencies": { "dependencies": {
"consola": "^2.15.3", "consola": "^2.15.3",
"create-require": "^1.1.1", "create-require": "^1.1.1",
"defu": "^3.2.2", "defu": "^3.2.2",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"hash-sum": "^2.0.0",
"jiti": "^1.6.4", "jiti": "^1.6.4",
"lodash": "^4.17.21",
"rc9": "^1.2.0", "rc9": "^1.2.0",
"scule": "^0.1.1", "scule": "^0.1.1",
"std-env": "^2.3.0", "std-env": "^2.3.0",
"ufo": "^0.6.10", "ufo": "^0.6.10",
"untyped": "^0.2.2" "unctx": "^0.0.3",
"untyped": "^0.2.2",
"upath": "^2.0.1"
}, },
"build": { "build": {
"prebuild": "yarn -s gentypes",
"entries": { "entries": {
"index": { "index": {
"format": "cjs" "format": "cjs"

View File

@ -3,7 +3,7 @@ import { mkdir, writeFile } from 'fs/promises'
import { resolveSchema, generateTypes, generateMarkdown } from 'untyped' import { resolveSchema, generateTypes, generateMarkdown } from 'untyped'
async function main () { async function main () {
const genDir = resolve(__dirname, '../dist/config') const genDir = resolve(__dirname, '../.gen')
const srcConfig = await import('../src/config/schema').then(r => r.default) const srcConfig = await import('../src/config/schema').then(r => r.default)
const defaults = { rootDir: '/<dir>/' } const defaults = { rootDir: '/<dir>/' }
@ -11,9 +11,9 @@ async function main () {
await mkdir(genDir).catch(() => { }) await mkdir(genDir).catch(() => { })
await writeFile(resolve(genDir, 'config.md'), generateMarkdown(schema)) await writeFile(resolve(genDir, 'config.md'), generateMarkdown(schema))
await writeFile(resolve(genDir, 'schema.json'), JSON.stringify(schema, null, 2)) await writeFile(resolve(genDir, 'config.schema.json'), JSON.stringify(schema, null, 2))
await writeFile(resolve(genDir, 'defaults.json'), JSON.stringify(defaults, null, 2)) await writeFile(resolve(genDir, 'config.defaults.json'), JSON.stringify(defaults, null, 2))
await writeFile(resolve(genDir, 'config.d.ts'), 'export default ' + generateTypes(schema, 'Config')) await writeFile(resolve(genDir, 'config.d.ts'), 'export ' + generateTypes(schema, 'ConfigSchema'))
} }
main().catch(console.error) main().catch(console.error)

View File

@ -3,6 +3,7 @@ import defu from 'defu'
import jiti from 'jiti' import jiti from 'jiti'
import { applyDefaults } from 'untyped' import { applyDefaults } from 'untyped'
import * as rc from 'rc9' import * as rc from 'rc9'
import { NuxtOptions } from '../types/config'
import nuxtConfigSchema from './schema' import nuxtConfigSchema from './schema'
export interface LoadNuxtConfigOptions { export interface LoadNuxtConfigOptions {
@ -11,7 +12,7 @@ export interface LoadNuxtConfigOptions {
config?: any config?: any
} }
export function loadNuxtConfig (opts: LoadNuxtConfigOptions) { export function loadNuxtConfig (opts: LoadNuxtConfigOptions): NuxtOptions {
const rootDir = resolve(process.cwd(), opts.rootDir || '.') const rootDir = resolve(process.cwd(), opts.rootDir || '.')
const _require = jiti(rootDir) const _require = jiti(rootDir)
@ -53,5 +54,5 @@ export function loadNuxtConfig (opts: LoadNuxtConfigOptions) {
} }
// Resolve and apply defaults // Resolve and apply defaults
return applyDefaults(nuxtConfigSchema, nuxtConfig) return applyDefaults(nuxtConfigSchema, nuxtConfig) as NuxtOptions
} }

View File

@ -112,10 +112,10 @@ export default {
}, },
extensions: { extensions: {
$resolve: val => ['js', 'mjs', 'ts', 'tsx', 'vue'].concat(val).filter(Boolean) $resolve: val => ['.js', '.mjs', '.ts', '.tsx', '.vue'].concat(val).filter(Boolean)
}, },
styleExtensions: ['css', 'pcss', 'postcss', 'styl', 'stylus', 'scss', 'sass', 'less'], styleExtensions: ['.css', '.pcss', '.postcss', '.styl', '.stylus', '.scss', '.sass', '.less'],
alias: { alias: {
$resolve: (val, get) => ({ $resolve: (val, get) => ({

View File

@ -0,0 +1,11 @@
export default {
_majorVersion: 2,
_legacyGenerate: false,
_start: false,
_build: false,
_generate: false,
_cli: false,
_requiredModules: {},
appDir: '',
vite: false
}

View File

@ -1,6 +1,7 @@
import _app from './_app' import _app from './_app'
import _common from './_common' import _common from './_common'
import _internal from './_internal'
import build from './build' import build from './build'
import messages from './messages' import messages from './messages'
import render from './render' import render from './render'
@ -30,6 +31,7 @@ TODO for top level normalizations: (nuxt2)
export default { export default {
..._app, ..._app,
..._common, ..._common,
..._internal,
build, build,
messages, messages,
render, render,

View File

@ -1,3 +1,22 @@
// Config
export * from './config/load' export * from './config/load'
export * from './config/env' export * from './config/env'
export * from './utils'
// Nuxt
export * from './nuxt'
// Module
export * from './module/container'
export * from './module/define'
export * from './module/install'
export * from './module/utils'
// Utils
export * from './utils/cjs'
export * from './utils/resolve'
// Types
export * from './types/config'
export * from './types/hooks'
export * from './types/module'
export * from './types/nuxt'

View File

@ -0,0 +1,73 @@
import type { Nuxt } from '../types/nuxt'
import type { NuxtOptions } from '../types/config'
import type { TemplateOpts, PluginTemplateOpts } from '../types/module'
import { nuxtCtx } from '../nuxt'
import { installModule } from './install'
import {
addTemplate,
addErrorLayout,
addLayout,
addPlugin,
addServerMiddleware,
extendBuild,
extendRoutes
} from './utils'
export class ModuleContainer {
nuxt: Nuxt
options: NuxtOptions
constructor (nuxt: Nuxt) {
this.nuxt = nuxt
this.options = nuxt.options
}
private _call<F extends (...args: any) => any>(fn: F, ...args: Parameters<F>): ReturnType<F> {
// @ts-ignore
return nuxtCtx.call(this.nuxt, () => fn(...args))
}
ready () {
return Promise.resolve()
}
addVendor () {
console.warn('addVendor has been deprecated')
}
addTemplate (tmpl: TemplateOpts | string) {
return this._call(addTemplate, tmpl)
}
addPlugin (tmpl: PluginTemplateOpts) {
return this._call(addPlugin, tmpl)
}
addLayout (tmpl: TemplateOpts, name: string) {
return this._call(addLayout, tmpl, name)
}
addErrorLayout (dst: string) {
return this._call(addErrorLayout, dst)
}
addServerMiddleware (middleware) {
return this._call(addServerMiddleware, middleware)
}
extendBuild (fn) {
return this._call(extendBuild, fn)
}
extendRoutes (fn) {
return this._call(extendRoutes, fn)
}
requireModule (moduleOpts) {
return installModule(this.nuxt, moduleOpts)
}
addModule (moduleOpts) {
return installModule(this.nuxt, moduleOpts)
}
}

View File

@ -0,0 +1,44 @@
import defu from 'defu'
import { applyDefaults } from 'untyped'
import { useNuxt, nuxtCtx } from '../nuxt'
import type { Nuxt } from '../types/nuxt'
import type { NuxtModule, LegacyNuxtModule, ModuleOptions } from '../types/module'
export function defineNuxtModule<OptionsT extends ModuleOptions> (input: NuxtModule<OptionsT> | ((nuxt: Nuxt) => NuxtModule<OptionsT>)): LegacyNuxtModule {
let mod: NuxtModule<OptionsT>
function wrappedModule (inlineOptions: OptionsT) {
// Get nuxt context
const nuxt: Nuxt = this.nuxt || useNuxt()
// Resolve function
if (typeof input === 'function') {
const fn = input
mod = nuxtCtx.call(nuxt, () => fn(nuxt))
} else {
mod = input
}
// Install hooks
if (mod.hooks) {
nuxt.hooks.addHooks(mod.hooks)
}
// Stop if no install provided
if (typeof mod.setup !== 'function') {
return
}
// Resolve options
const configKey = mod.configKey || mod.name
const userOptions = defu(inlineOptions, nuxt.options[configKey]) as OptionsT
const resolvedOptions = applyDefaults(mod.defaults as any, userOptions) as OptionsT
// Call setup
return nuxtCtx.call(nuxt, () => mod.setup.call(null, resolvedOptions, nuxt))
}
wrappedModule.meta = mod
return wrappedModule
}

View File

@ -0,0 +1,60 @@
import type { Nuxt } from '../types/nuxt'
import type { LegacyNuxtModule, NuxtModule, ModuleMeta, ModuleInstallOptions, ModuleOptions, ModuleSrc } from '../types/module'
import { requireModule } from '../utils/cjs'
import { nuxtCtx } from '../nuxt'
import { defineNuxtModule } from './define'
import { ModuleContainer } from './container'
export async function installModule (nuxt: Nuxt, installOpts: ModuleInstallOptions) {
let src: ModuleSrc
let options: ModuleOptions = {}
const meta: ModuleMeta = {}
// Extract src, meta and options
if (typeof installOpts === 'string') {
src = installOpts
} else if (Array.isArray(installOpts)) {
[src, options] = installOpts
} else if (typeof installOpts === 'object') {
if (installOpts.src || installOpts.handler) {
src = installOpts.src || installOpts.handler
options = installOpts.options
Object.assign(meta, installOpts.meta)
} else {
src = installOpts as NuxtModule
}
} else {
src = installOpts
}
// Resolve as legacy handler
let handler: LegacyNuxtModule
if (typeof src === 'string') {
handler = requireModule(src)
if (!meta.name) {
meta.name = src
}
} else if (typeof src === 'function') {
handler = src
} else {
handler = defineNuxtModule(src)
}
// Merge meta
if (handler.meta) {
Object.assign(meta, handler.meta)
}
// Ensure module is required once
if (typeof meta.name === 'string') {
nuxt.options._requiredModules = nuxt.options._requiredModules || {}
if (nuxt.options._requiredModules[meta.name]) {
return
}
nuxt.options._requiredModules[meta.name] = true
}
// Execute in legacy container
const container = new ModuleContainer(nuxt)
await nuxtCtx.call(nuxt, () => handler.call(container, options))
}

View File

@ -0,0 +1,100 @@
import path, { basename, parse } from 'path'
import fs from 'fs'
import hash from 'hash-sum'
import consola from 'consola'
import { useNuxt } from '../nuxt'
import { chainFn } from '../utils/task'
import type { TemplateOpts, PluginTemplateOpts } from '../types/module'
export function addTemplate (tmpl: TemplateOpts | string) {
const nuxt = useNuxt()
if (!tmpl) {
throw new Error('Invalid tmpl: ' + JSON.stringify(tmpl))
}
// Validate & parse source
const src = typeof tmpl === 'string' ? tmpl : tmpl.src
const srcPath = parse(src)
if (typeof src !== 'string' || !fs.existsSync(src)) {
throw new Error('tmpl src not found: ' + src)
}
// Mostly for DX, some people prefers `filename` vs `fileName`
const fileName = typeof tmpl === 'string' ? '' : tmpl.fileName || tmpl.filename
// Generate unique and human readable dst filename if not provided
const dst = fileName || `${basename(srcPath.dir)}.${srcPath.name}.${hash(src)}${srcPath.ext}`
// Add to tmpls list
const tmplObj = {
src,
dst,
options: typeof tmpl === 'string' ? undefined : tmpl.options
}
nuxt.options.build.templates.push(tmplObj)
return tmplObj
}
export function addPlugin (tmpl: PluginTemplateOpts) {
const nuxt = useNuxt()
const { dst } = addTemplate(tmpl)
if (!tmpl.mode && typeof tmpl.ssr === 'boolean') {
tmpl.mode = tmpl.ssr ? 'server' : 'client'
}
// Add to nuxt plugins
nuxt.options.plugins.unshift({
src: path.join(nuxt.options.buildDir, dst),
mode: tmpl.mode
})
}
export function addLayout (tmpl: TemplateOpts, name: string) {
const nuxt = useNuxt()
const { dst, src } = addTemplate(tmpl)
const layoutName = name || path.parse(src).name
const layout = nuxt.options.layouts[layoutName]
if (layout) {
consola.warn(`Duplicate layout registration, "${layoutName}" has been registered as "${layout}"`)
}
// Add to nuxt layouts
nuxt.options.layouts[layoutName] = `./${dst}`
// If error layout, set ErrorPage
if (name === 'error') {
addErrorLayout(dst)
}
}
export function addErrorLayout (dst: string) {
const nuxt = useNuxt()
const relativeBuildDir = path.relative(nuxt.options.rootDir, nuxt.options.buildDir)
nuxt.options.ErrorPage = `~/${relativeBuildDir}/${dst}`
}
export function addServerMiddleware (middleware) {
const nuxt = useNuxt()
nuxt.options.serverMiddleware.push(middleware)
}
export function extendBuild (fn) {
const nuxt = useNuxt()
// @ts-ignore TODO
nuxt.options.build.extend = chainFn(nuxt.options.build.extend, fn)
}
export function extendRoutes (fn) {
const nuxt = useNuxt()
nuxt.options.router.extendRoutes = chainFn(nuxt.options.router.extendRoutes, fn)
}

10
packages/kit/src/nuxt.ts Normal file
View File

@ -0,0 +1,10 @@
import { getContext } from 'unctx'
import type { Nuxt } from './types/nuxt'
import type { NuxtConfig } from './types/config'
export const nuxtCtx = getContext<Nuxt>('nuxt')
export const useNuxt = nuxtCtx.use
export function defineNuxtConfig (config: NuxtConfig) {
return config
}

View File

@ -0,0 +1,15 @@
import { ConfigSchema } from '../../.gen/config'
import { ModuleInstallOptions } from './module'
import { NuxtHooks } from './hooks'
export interface TypedConfigSchema extends ConfigSchema {
hooks: NuxtHooks,
modules: ModuleInstallOptions[]
buildModules: ModuleInstallOptions[]
}
export interface NuxtOptions extends TypedConfigSchema { }
type DeepPartial<T> = { [P in keyof T]?: DeepPartial<T[P]> | T[P] }
export interface NuxtConfig extends DeepPartial<TypedConfigSchema> { }

View File

@ -0,0 +1,13 @@
import { Nuxt } from './nuxt'
export type NuxtHook<Arg1 = any> = (arg1: Arg1, ...args: any) => Promise<void> | void
export interface NuxtHooks {
[key: string]: NuxtHook
'modules:before': NuxtHook<Nuxt>
'modules:done': NuxtHook<Nuxt>
'ready': NuxtHook<Nuxt>
}
export type NuxtHookName = keyof NuxtHooks

View File

@ -0,0 +1,51 @@
import type { ModuleContainer } from '../module/container'
import { Nuxt } from './nuxt'
import { NuxtHooks } from './hooks'
export interface ModuleMeta {
name?: string
configKey?: string
[key: string]: any
}
export type ModuleOptions = Record<string, any>
export interface LegacyNuxtModule {
(this: ModuleContainer, inlineOptions?: ModuleOptions): void | Promise<void>
meta?: ModuleMeta
}
export interface NuxtModule<T extends ModuleOptions = any> extends ModuleMeta {
defaults?: T
setup?: (this: null, resolvedOptions: T, nuxt: Nuxt) => void | Promise<void>
hooks?: Partial<NuxtHooks>
}
export type ModuleSrc = string | NuxtModule | LegacyNuxtModule
export interface ModuleInstallOptionsObj {
src: ModuleSrc,
meta: ModuleMeta
options: ModuleOptions
handler: LegacyNuxtModule
}
export type ModuleInstallOptions =
ModuleSrc |
[ModuleSrc, ModuleOptions?] |
Partial<ModuleInstallOptionsObj>
// -- Templates --
export interface TemplateOpts {
filename?: string
fileName?: string
options?: Record<string, any>
src: string
}
export interface PluginTemplateOpts extends TemplateOpts {
/** @deprecated use mode */
ssr?: boolean
mode?: 'all' | 'server' | 'client'
}

View File

@ -0,0 +1,16 @@
import { NuxtHookName, NuxtHook, NuxtHooks } from './hooks'
import { NuxtOptions } from './config'
export interface Nuxt {
options: NuxtOptions
hooks: {
hook(hookName: NuxtHookName, callback: NuxtHook)
callHook<T extends string>(hookbname: T, ...args: Parameters<NuxtHooks[T]>)
addHooks(hooks: Partial<NuxtHooks>)
}
hook: Nuxt['hooks']['hook']
callHook: Nuxt['hooks']['callHook']
server?: any
}

View File

@ -1,18 +1,33 @@
import { join } from 'path' import { join } from 'path'
import jiti from 'jiti'
export function isExternalDependency (id: string) { // TODO: use create-require for jest environment
const _require = jiti(process.cwd())
export interface ResolveModuleOptions {
paths?: string[]
}
export interface RequireModuleOptions extends ResolveModuleOptions {
native?: boolean
clearCache?: boolean
interopDefault?: boolean
}
export function isNodeModules (id: string) {
// TODO: Follow symlinks
return /[/\\]node_modules[/\\]/.test(id) return /[/\\]node_modules[/\\]/.test(id)
} }
export function clearRequireCache (id: string) { export function clearRequireCache (id: string) {
if (isExternalDependency(id)) { if (isNodeModules(id)) {
return return
} }
const entry = getRequireCacheItem(id) const entry = getRequireCacheItem(id)
if (!entry) { if (!entry) {
delete require.cache[id] delete _require.cache[id]
return return
} }
@ -24,11 +39,11 @@ export function clearRequireCache (id: string) {
clearRequireCache(child.id) clearRequireCache(child.id)
} }
delete require.cache[id] delete _require.cache[id]
} }
export function scanRequireTree (id: string, files = new Set<string>()) { export function scanRequireTree (id: string, files = new Set<string>()) {
if (isExternalDependency(id) || files.has(id)) { if (isNodeModules(id) || files.has(id)) {
return files return files
} }
@ -50,18 +65,54 @@ export function scanRequireTree (id: string, files = new Set<string>()) {
export function getRequireCacheItem (id: string) { export function getRequireCacheItem (id: string) {
try { try {
return require.cache[id] return _require.cache[id]
} catch (e) { } catch (e) {
} }
} }
export function tryRequire (id: string) { export function requireModulePkg (id: string, opts: RequireModuleOptions = {}) {
return requireModule(join(id, 'package.json'), opts)
}
export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
return _require.resolve(id, {
paths: opts.paths
})
}
export function tryResolveModule (path: string, opts: ResolveModuleOptions = {}) {
try { try {
return require(id) return resolveModule(path, opts)
} catch (e) { } catch (error) {
if (error.code !== 'MODULE_NOT_FOUND') {
throw error
}
} }
} }
export function getPKG (id: string) { export function requireModule (id: string, opts: RequireModuleOptions = {}) {
return tryRequire(join(id, 'package.json')) // Resolve id
const resolvedPath = resolveModule(id, opts)
// Clear require cache if necessary
if (opts.clearCache && !isNodeModules(id)) {
clearRequireCache(resolvedPath)
}
// Try to require
let requiredModule = _require(resolvedPath)
// Interop default
if (opts.interopDefault !== false && requiredModule && requiredModule.default) {
requiredModule = requiredModule.default
}
return requiredModule
}
export function tryRequireModule (id: string, opts: RequireModuleOptions = {}) {
try {
return requireModule(id, opts)
} catch (e) {
}
} }

View File

@ -1,13 +0,0 @@
export const TARGETS = {
server: 'server',
static: 'static'
} as const
export type Target = keyof typeof TARGETS
export const MODES = {
universal: 'universal',
spa: 'spa'
} as const
export type Mode = keyof typeof MODES

View File

@ -1,36 +0,0 @@
import type { ServerResponse, IncomingMessage } from 'http'
import { TARGETS } from './constants'
export const getContext = function getContext (req: IncomingMessage, res: ServerResponse) {
return { req, res }
}
type NuxtGlobal = string | ((globalName: string) => string)
type Globals = 'id' | 'nuxt' | 'context' | 'pluginPrefix' | 'readyCallback' | 'loadedCallback'
type NuxtGlobals = {
[key in Globals]: NuxtGlobal
}
export type DeterminedGlobals = {
[key in keyof NuxtGlobals]: string
}
export const determineGlobals = function determineGlobals (globalName: string, globals: NuxtGlobals) {
const _globals: Partial<DeterminedGlobals> = {}
for (const global in globals) {
const currentGlobal = globals[global]
if (currentGlobal instanceof Function) {
_globals[global] = currentGlobal(globalName)
} else {
_globals[global] = currentGlobal
}
}
return _globals as DeterminedGlobals
}
export const isFullStatic = function (options) {
return !options.dev && !options._legacyGenerate && options.target === TARGETS.static && options.render.ssr
}

View File

@ -1,7 +0,0 @@
export * from './context'
export * from './lang'
export * from './resolve'
export * from './route'
export * from './task'
export * from './cjs'
export * from './constants'

View File

@ -1,47 +0,0 @@
export const encodeHtml = function encodeHtml (str: string) {
return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
export const isString = (obj: unknown): obj is string =>
typeof obj === 'string' || obj instanceof String
export const isNonEmptyString = (obj: unknown): obj is string =>
Boolean(obj && isString(obj))
export const isPureObject = (
obj: unknown
): obj is Exclude<object, Array<any>> =>
!Array.isArray(obj) && typeof obj === 'object'
export const isUrl = function isUrl (url: string) {
return ['http', '//'].some(str => url.startsWith(str))
}
export const urlJoin = function urlJoin (...args: string[]) {
return [].slice
.call(args)
.join('/')
.replace(/\/+/g, '/')
.replace(':/', '://')
}
/**
* Wraps value in array if it is not already an array
*/
export const wrapArray = <T>(value: T | T[]): T[] =>
Array.isArray(value) ? value : [value]
const WHITESPACE_REPLACEMENTS: [RegExp, string][] = [
[/[ \t\f\r]+\n/g, '\n'], // strip empty indents
[/{\n{2,}/g, '{\n'], // strip start padding from blocks
[/\n{2,}([ \t\f\r]*})/g, '\n$1'], // strip end padding from blocks
[/\n{3,}/g, '\n\n'], // strip multiple blank lines (1 allowed)
[/\n{2,}$/g, '\n'] // strip blank lines EOF (0 allowed)
]
export const stripWhitespace = function stripWhitespace (string: string) {
WHITESPACE_REPLACEMENTS.forEach(([regex, newSubstr]) => {
string = string.replace(regex, newSubstr)
})
return string
}

View File

@ -1,123 +1,72 @@
import path from 'path' import { existsSync, lstatSync } from 'fs'
import consola from 'consola' import { resolve, join } from 'upath'
import escapeRegExp from 'lodash/escapeRegExp'
export const startsWithAlias = (aliasArray: string[]) => (str: string) => export interface ResolveOptions {
aliasArray.some(c => str.startsWith(c)) base?: string
alias?: Record<string, string>
export const startsWithSrcAlias = startsWithAlias(['@', '~']) extensions?: string[]
export const startsWithRootAlias = startsWithAlias(['@@', '~~'])
export const isWindows = process.platform.startsWith('win')
export const wp = function wp (p = '') {
if (isWindows) {
return p.replace(/\\/g, '\\\\')
}
return p
} }
// Kept for backward compat (modules may use it from template context) function resolvePath (path: string, opts: ResolveOptions = {}) {
export const wChunk = function wChunk (p = '') { // Fast return in case of path exists
return p if (existsSync(path)) {
} return path
const reqSep = /\//g
const sysSep = escapeRegExp(path.sep)
const normalize = (string: string) => string.replace(reqSep, sysSep)
export const r = function r (...args: string[]) {
const lastArg = args[args.length - 1]
if (startsWithSrcAlias(lastArg)) {
return wp(lastArg)
} }
return wp(path.resolve(...args.map(normalize))) let resolvedPath: string
}
export const relativeTo = function relativeTo (dir: string, ...args: string[]): string { // Resolve alias
// Keep webpack inline loader intact if (opts.alias) {
if (args[0].includes('!')) { resolvedPath = resolveAlias(path, opts.alias)
const loaders = args.shift()!.split('!')
return loaders.concat(relativeTo(dir, loaders.pop()!, ...args)).join('!')
} }
// Resolve path // Resolve relative to base or cwd
const resolvedPath = r(...args) resolvedPath = resolve(opts.base || '.', resolvedPath)
// Check if path is an alias // Check if resolvedPath is a file
if (startsWithSrcAlias(resolvedPath)) { let isDirectory = false
if (existsSync(resolvedPath)) {
isDirectory = lstatSync(resolvedPath).isDirectory()
if (!isDirectory) {
return resolvedPath
}
}
// Check possible extensions
for (const ext of opts.extensions) {
// resolvedPath.[ext]
const resolvedPathwithExt = resolvedPath + ext
if (!isDirectory && existsSync(resolvedPathwithExt)) {
return resolvedPathwithExt
}
// resolvedPath/index.[ext]
const resolvedPathwithIndex = join(resolvedPath, 'index' + ext)
if (isDirectory && existsSync(resolvedPathwithIndex)) {
return resolvedPathwithIndex
}
}
// If extension check fails and resolvedPath is a valid directory, return it
if (isDirectory) {
return resolvedPath return resolvedPath
} }
// Make correct relative path // Give up if it is neither a directory
let rp = path.relative(dir, resolvedPath) throw new Error(`Cannot resolve "${path}" from "${resolvedPath}"`)
if (rp[0] !== '.') {
rp = '.' + path.sep + rp
}
return wp(rp)
} }
interface AliasOptions { export function resolveAlias (path: string, alias: ResolveOptions['alias']) {
bind?: boolean for (const key in alias) {
warn?: boolean if (path.startsWith(key)) {
} path = alias[key] + path.substr(key.length)
export function defineAlias <O extends Record<string, any>> (
src: O,
target: Record<string, any>,
prop: string | string[],
opts: AliasOptions = {}
) {
const { bind = true, warn = false } = opts
if (Array.isArray(prop)) {
for (const p of prop) {
defineAlias(src, target, p, opts)
} }
return
} }
return path
let targetVal = target[prop]
if (bind && typeof targetVal === 'function') {
targetVal = targetVal.bind(target)
}
let warned = false
Object.defineProperty(src, prop, {
get: () => {
if (warn && !warned) {
warned = true
consola.warn({
message: `'${prop}' is deprecated'`,
// eslint-disable-next-line unicorn/error-message
additional: new Error().stack.split('\n').splice(2).join('\n')
})
}
return targetVal
}
})
} }
const isIndex = (s: string) => /(.*)\/index\.[^/]+$/.test(s) export function tryResolvePath (path: string, opts: ResolveOptions = {}) {
try {
export function isIndexFileAndFolder (pluginFiles: string[]) { return resolvePath(path, opts)
// Return early in case the matching file count exceeds 2 (index.js + folder) } catch (e) {
if (pluginFiles.length !== 2) {
return false
} }
return pluginFiles.some(isIndex)
}
export const getMainModule = () => {
return (
require.main ||
(module && ((module as any).main as NodeJS.Module)) ||
module
)
} }

View File

@ -1,25 +0,0 @@
import path from 'path'
import get from 'lodash/get'
import consola from 'consola'
// Guard dir1 from dir2 which can be indiscriminately removed
export const guardDir = function guardDir (options: Record<string, any>, key1: string, key2: string) {
const dir1 = get(options, key1, false) as string
const dir2 = get(options, key2, false) as string
if (
dir1 &&
dir2 &&
(
dir1 === dir2 ||
(
dir1.startsWith(dir2) &&
!path.basename(dir1).startsWith(path.basename(dir2))
)
)
) {
const errorMessage = `options.${key2} cannot be a parent of or same as ${key1}`
consola.fatal(errorMessage)
throw new Error(errorMessage)
}
}

View File

@ -1,4 +1,4 @@
export const sequence = function sequence<T, R> ( export function sequence<T, R> (
tasks: T[], tasks: T[],
fn: (task: T) => R fn: (task: T) => R
) { ) {
@ -8,14 +8,14 @@ export const sequence = function sequence<T, R> (
) )
} }
export const parallel = function parallel<T, R> ( export function parallel<T, R> (
tasks: T[], tasks: T[],
fn: (task: T) => R fn: (task: T) => R
) { ) {
return Promise.all(tasks.map(fn)) return Promise.all(tasks.map(fn))
} }
export const chainFn = function chainFn (base, fn) { export function chainFn (base, fn) {
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
return base return base
} }

View File

@ -9,13 +9,19 @@
], ],
"scripts": { "scripts": {
"build": "jiti ../../scripts/build .", "build": "jiti ../../scripts/build .",
"stub": "yarn build --stub",
"prepublishOnly": "yarn build" "prepublishOnly": "yarn build"
}, },
"build": { "build": {
"externals": [
"@nuxt/kit"
],
"entries": { "entries": {
"index": { "format": "cjs" }, "index": {
"compat": { "format": "cjs" }, "format": "cjs"
},
"compat": {
"format": "cjs"
},
"runtime/": {} "runtime/": {}
}, },
"dependencies": [ "dependencies": [
@ -77,6 +83,7 @@
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
"std-env": "^2.3.0", "std-env": "^2.3.0",
"table": "^6.0.7", "table": "^6.0.7",
"ufo": "^0.6.10",
"upath": "^2.0.1", "upath": "^2.0.1",
"vue-bundle-renderer": "^0.2.3", "vue-bundle-renderer": "^0.2.3",
"vue-server-renderer": "^2.6.12" "vue-server-renderer": "^2.6.12"

View File

@ -57,8 +57,7 @@ export default function nuxt2CompatModule () {
// Resolve middleware // Resolve middleware
nuxt.hook('modules:done', () => { nuxt.hook('modules:done', () => {
const { middleware, legacyMiddleware } = const { middleware, legacyMiddleware } = resolveMiddleware(nuxt.options.serverMiddleware)
resolveMiddleware(nuxt.options.serverMiddleware, nuxt.resolver.resolvePath)
if (nuxt.server) { if (nuxt.server) {
nuxt.server.setLegacyMiddleware(legacyMiddleware) nuxt.server.setLegacyMiddleware(legacyMiddleware)
} }

View File

@ -1,6 +1,6 @@
import { resolve } from 'upath' import { resolve, dirname } from 'upath'
import defu from 'defu' import defu from 'defu'
import type { NuxtOptions } from '@nuxt/types' import type { NuxtOptions } from '@nuxt/kit'
import Hookable, { configHooksT } from 'hookable' import Hookable, { configHooksT } from 'hookable'
import type { Preset } from '@nuxt/un' import type { Preset } from '@nuxt/un'
import { tryImport, resolvePath, detectTarget, extendPreset } from './utils' import { tryImport, resolvePath, detectTarget, extendPreset } from './utils'
@ -106,7 +106,7 @@ export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): N
} }
}, },
_internal: { _internal: {
runtimeDir: resolve(__dirname, './runtime'), runtimeDir: resolve(dirname(require.resolve('@nuxt/nitro')), 'runtime'),
hooks: new Hookable() hooks: new Hookable()
} }
} }

View File

@ -27,6 +27,11 @@ export function externals (opts: NodeExternalsOptions): Plugin {
return null return null
} }
// Bundle ts
if (id.endsWith('.ts')) {
return null
}
for (const dir of opts.moduleDirectories) { for (const dir of opts.moduleDirectories) {
if (id.startsWith(dir)) { if (id.startsWith(dir)) {
id = id.substr(dir.length + 1) id = id.substr(dir.length + 1)

View File

@ -2,6 +2,7 @@ import { resolve, join, extname } from 'upath'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
import globby from 'globby' import globby from 'globby'
import { watch } from 'chokidar' import { watch } from 'chokidar'
import { tryResolvePath } from '@nuxt/kit'
export interface ServerMiddleware { export interface ServerMiddleware {
route: string route: string
@ -50,7 +51,7 @@ export function scanMiddleware (serverDir: string, onChange?: (results: ServerMi
return scan() return scan()
} }
export function resolveMiddleware (serverMiddleware: any[], resolvePath: (string) => string) { export function resolveMiddleware (serverMiddleware: any[]) {
const middleware: ServerMiddleware[] = [] const middleware: ServerMiddleware[] = []
const legacyMiddleware: ServerMiddleware[] = [] const legacyMiddleware: ServerMiddleware[] = []
@ -65,7 +66,9 @@ export function resolveMiddleware (serverMiddleware: any[], resolvePath: (string
delete m.path delete m.path
middleware.push({ middleware.push({
...m, ...m,
handle: resolvePath(handle), handle: tryResolvePath(handle, {
extensions: ['.ts', '.js']
}),
route route
}) })
} }

View File

@ -13,10 +13,12 @@
], ],
"scripts": { "scripts": {
"build": "jiti ../../scripts/build .", "build": "jiti ../../scripts/build .",
"stub": "yarn build --stub",
"prepublishOnly": "yarn build" "prepublishOnly": "yarn build"
}, },
"build": { "build": {
"externals": [
"nuxt3"
],
"entries": { "entries": {
"index": { "format": "cjs" } "index": { "format": "cjs" }
} }

View File

@ -15,7 +15,10 @@ async function _main () {
const { loadNuxt, build } = await import('nuxt3') const { loadNuxt, build } = await import('nuxt3')
const nuxt = await loadNuxt({ for: isDev ? 'dev' : 'build', rootDir }) const nuxt = await loadNuxt({
for: isDev ? 'dev' : 'build',
rootDir
})
if (isDev) { if (isDev) {
// https://github.com/nuxt-contrib/listhen // https://github.com/nuxt-contrib/listhen

View File

@ -9,7 +9,6 @@
], ],
"scripts": { "scripts": {
"build": "jiti ../../scripts/build .", "build": "jiti ../../scripts/build .",
"stub": "yarn build --stub",
"prepublishOnly": "yarn build" "prepublishOnly": "yarn build"
}, },
"build": { "build": {
@ -17,6 +16,7 @@
"index": { "format": "cjs" } "index": { "format": "cjs" }
}, },
"dependencies": [ "dependencies": [
"@nuxt/app",
"@vue/compiler-sfc", "@vue/compiler-sfc",
"postcss", "postcss",
"postcss-loader", "postcss-loader",
@ -38,6 +38,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "0.0.0",
"@nuxt/app": "0.0.0", "@nuxt/app": "0.0.0",
"@nuxt/friendly-errors-webpack-plugin": "^2.5.0", "@nuxt/friendly-errors-webpack-plugin": "^2.5.0",
"@nuxt/nitro": "^0.1.11", "@nuxt/nitro": "^0.1.11",
@ -50,12 +51,9 @@
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"consola": "^2.15.3", "consola": "^2.15.3",
"core-js": "^3.9.1", "core-js": "^3.9.1",
"create-require": "^1.1.1",
"css-loader": "^5.2.0", "css-loader": "^5.2.0",
"css-minimizer-webpack-plugin": "^1.3.0", "css-minimizer-webpack-plugin": "^1.3.0",
"defu": "^3.2.2", "defu": "^3.2.2",
"destr": "^1.1.0",
"dotenv": "^8.2.0",
"esbuild-loader": "^2.11.0", "esbuild-loader": "^2.11.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fs-extra": "^9.1.0", "fs-extra": "^9.1.0",
@ -64,7 +62,6 @@
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"hookable": "^4.4.1", "hookable": "^4.4.1",
"ignore": "^5.1.8", "ignore": "^5.1.8",
"jiti": "^1.6.4",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"memfs": "^3.2.0", "memfs": "^3.2.0",
"mini-css-extract-plugin": "^1.4.0", "mini-css-extract-plugin": "^1.4.0",
@ -72,9 +69,7 @@
"postcss": "^8.2.8", "postcss": "^8.2.8",
"postcss-import-resolver": "^2.0.0", "postcss-import-resolver": "^2.0.0",
"postcss-loader": "^5.2.0", "postcss-loader": "^5.2.0",
"rc9": "^1.2.0",
"scule": "^0.1.1", "scule": "^0.1.1",
"std-env": "3.0.0-alpha",
"style-resources-loader": "^1.4.1", "style-resources-loader": "^1.4.1",
"time-fix-plugin": "^2.0.7", "time-fix-plugin": "^2.0.7",
"ufo": "^0.6.10", "ufo": "^0.6.10",

View File

@ -1,8 +1,10 @@
import { resolve } from 'path' import { resolve } from 'path'
import defu from 'defu' import defu from 'defu'
import { tryResolvePath } from '@nuxt/kit'
import { Builder } from './builder' import { Builder } from './builder'
import { NuxtRoute, resolvePagesRoutes } from './pages' import { NuxtRoute, resolvePagesRoutes } from './pages'
import { NuxtPlugin, resolvePlugins } from './plugins' import { NuxtPlugin, resolvePlugins } from './plugins'
export interface NuxtApp { export interface NuxtApp {
main?: string main?: string
routes: NuxtRoute[] routes: NuxtRoute[]
@ -35,10 +37,15 @@ export async function createApp (
}) })
// Resolve app.main // Resolve app.main
const resolveOptions = {
base: nuxt.options.srcDir,
alias: nuxt.options.alias,
extensions: nuxt.options.extensions
}
if (!app.main) { if (!app.main) {
app.main = app.main = tryResolvePath('~/App', resolveOptions) ||
nuxt.resolver.tryResolvePath('~/App') || tryResolvePath('~/app', resolveOptions)
nuxt.resolver.tryResolvePath('~/app')
} }
// Resolve pages/ // Resolve pages/

View File

@ -1,8 +1,8 @@
import { join, relative } from 'path' import { join, relative } from 'path'
import fsExtra from 'fs-extra' import fsExtra from 'fs-extra'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import { DeterminedGlobals, determineGlobals } from '@nuxt/kit' import { Nuxt } from '@nuxt/kit'
import { Nuxt } from './nuxt'
import { import {
templateData, templateData,
compileTemplates, compileTemplates,
@ -15,14 +15,13 @@ import Ignore from './utils/ignore'
export class Builder { export class Builder {
nuxt: Nuxt nuxt: Nuxt
globals: DeterminedGlobals globals: any
ignore: Ignore ignore: Ignore
templates: NuxtTemplate[] templates: NuxtTemplate[]
app: NuxtApp app: NuxtApp
constructor (nuxt) { constructor (nuxt) {
this.nuxt = nuxt this.nuxt = nuxt
this.globals = determineGlobals(nuxt.options.globalName, nuxt.options.globals)
this.ignore = new Ignore({ this.ignore = new Ignore({
rootDir: nuxt.options.srcDir, rootDir: nuxt.options.srcDir,
ignoreArray: nuxt.options.ignore.concat( ignoreArray: nuxt.options.ignore.concat(
@ -53,7 +52,7 @@ async function _build (builder: Builder) {
await bundle(builder) await bundle(builder)
await nuxt.callHook('build:done') await nuxt.callHook('build:done', builder)
} }
function watch (builder: Builder) { function watch (builder: Builder) {

View File

@ -1,2 +1,2 @@
export { Nuxt, loadNuxt } from './nuxt' export * from './nuxt'
export { build } from './builder' export * from './builder'

View File

@ -1,235 +0,0 @@
import path from 'path'
import fs from 'fs'
import hash from 'hash-sum'
import consola from 'consola'
import { chainFn, sequence } from '@nuxt/kit'
import Nuxt from './nuxt'
interface TemplateInput {
filename?: string
fileName?: string
options?: Record<string, any>
src: string
ssr?: boolean
mode?: 'all' | 'server' | 'client'
}
export default class ModuleContainer {
nuxt: Nuxt
options: Nuxt['options']
requiredModules: Record<string, {
src: string
options: Record<string, any>
handler
}>
constructor (nuxt: Nuxt) {
this.nuxt = nuxt
this.options = nuxt.options
this.requiredModules = {}
// Self bind to allow destructre from container
for (const method of Object.getOwnPropertyNames(ModuleContainer.prototype)) {
if (typeof this[method] === 'function') {
this[method] = this[method].bind(this)
}
}
}
async ready () {
// Call before hook
await this.nuxt.callHook('modules:before', this, this.options.modules)
if (this.options.buildModules && !this.options._start) {
// Load every devModule in sequence
await sequence(this.options.buildModules, this.addModule)
}
// Load every module in sequence
await sequence(this.options.modules, this.addModule)
// Load ah-hoc modules last
await sequence(this.options._modules, this.addModule)
// Call done hook
await this.nuxt.callHook('modules:done', this)
}
addVendor () {
consola.warn('addVendor has been deprecated due to webpack4 optimization')
}
addTemplate (template: TemplateInput | string) {
if (!template) {
throw new Error('Invalid template: ' + JSON.stringify(template))
}
// Validate & parse source
const src = typeof template === 'string' ? template : template.src
const srcPath = path.parse(src)
if (typeof src !== 'string' || !fs.existsSync(src)) {
throw new Error('Template src not found: ' + src)
}
// Mostly for DX, some people prefers `filename` vs `fileName`
const fileName = typeof template === 'string' ? '' : template.fileName || template.filename
// Generate unique and human readable dst filename if not provided
const dst = fileName || `${path.basename(srcPath.dir)}.${srcPath.name}.${hash(src)}${srcPath.ext}`
// Add to templates list
const templateObj = {
src,
dst,
options: typeof template === 'string' ? undefined : template.options
}
this.options.build.templates.push(templateObj)
return templateObj
}
addPlugin (template: TemplateInput) {
const { dst } = this.addTemplate(template)
// Add to nuxt plugins
this.options.plugins.unshift({
src: path.join(this.options.buildDir, dst),
// TODO: remove deprecated option in Nuxt 3
ssr: template.ssr,
mode: template.mode
})
}
addLayout (template: TemplateInput, name: string) {
const { dst, src } = this.addTemplate(template)
const layoutName = name || path.parse(src).name
const layout = this.options.layouts[layoutName]
if (layout) {
consola.warn(`Duplicate layout registration, "${layoutName}" has been registered as "${layout}"`)
}
// Add to nuxt layouts
this.options.layouts[layoutName] = `./${dst}`
// If error layout, set ErrorPage
if (name === 'error') {
this.addErrorLayout(dst)
}
}
addErrorLayout (dst: string) {
const relativeBuildDir = path.relative(this.options.rootDir, this.options.buildDir)
this.options.ErrorPage = `~/${relativeBuildDir}/${dst}`
}
addServerMiddleware (middleware) {
this.options.serverMiddleware.push(middleware)
}
extendBuild (fn) {
this.options.build.extend = chainFn(this.options.build.extend, fn)
}
extendRoutes (fn) {
this.options.router.extendRoutes = chainFn(
this.options.router.extendRoutes,
fn
)
}
requireModule (moduleOpts) {
return this.addModule(moduleOpts)
}
async addModule (moduleOpts) {
let src
let options: Record<string, any>
let handler
// Type 1: String or Function
if (typeof moduleOpts === 'string' || typeof moduleOpts === 'function') {
src = moduleOpts
} else if (Array.isArray(moduleOpts)) {
// Type 2: Babel style array
[src, options] = moduleOpts
} else if (typeof moduleOpts === 'object') {
// Type 3: Pure object
({ src, options, handler } = moduleOpts)
}
// Define handler if src is a function
if (src instanceof Function) {
handler = src
}
// Prevent adding buildModules-listed entries in production
if (this.options.buildModules.includes(handler) && this.options._start) {
return
}
// Resolve handler
if (!handler && typeof src === 'string') {
try {
handler = this.nuxt.resolver.requireModule(src, { useESM: true })
} catch (error) {
if (error.code !== 'MODULE_NOT_FOUND') {
throw error
}
// Hint only if entrypoint is not found and src is not local alias or path
if (error.message.includes(src) && !/^[~.]|^@\//.test(src)) {
let message = 'Module `{name}` not found.'
if (this.options.buildModules.includes(src)) {
message += ' Please ensure `{name}` is in `devDependencies` and installed. HINT: During build step, for npm/yarn, `NODE_ENV=production` or `--production` should NOT be used.'.replace('{name}', src)
} else if (this.options.modules.includes(src)) {
message += ' Please ensure `{name}` is in `dependencies` and installed.'
}
message = message.replace(/{name}/g, src)
consola.warn(message)
}
if (this.options._cli) {
throw error
} else {
// TODO: Remove in next major version
consola.warn('Silently ignoring module as programatic usage detected.')
return
}
}
}
// Validate handler
if (typeof handler !== 'function') {
throw new TypeError('Module should export a function: ' + src)
}
// Ensure module is required once
if ('meta' in handler && typeof src === 'string') {
const metaKey = handler.meta && handler.meta.name
const key = metaKey || src
if (typeof key === 'string') {
if (this.requiredModules[key]) {
if (!metaKey) {
// TODO: Skip with nuxt3
consola.warn('Modules should be only specified once:', key)
} else {
return
}
}
this.requiredModules[key] = { src, options, handler }
}
}
// Default module options to empty object
if (options === undefined) {
options = {}
}
const result = await handler.call(this, options)
return result
}
}

View File

@ -1,19 +1,18 @@
// eslint-disable-next-line import/named
import { wpfs, getNitroContext, createDevServer, resolveMiddleware, build, prepare, generate } from '@nuxt/nitro' import { wpfs, getNitroContext, createDevServer, resolveMiddleware, build, prepare, generate } from '@nuxt/nitro'
import type { Nuxt } from './index' import type { Nuxt } from '@nuxt/kit'
export function initNitro (nuxt: Nuxt) { export function initNitro (nuxt: Nuxt) {
// Create contexts // Create contexts
const nitroContext = getNitroContext(nuxt.options, nuxt.options.nitro || {}) const nitroContext = getNitroContext(nuxt.options, (nuxt.options as any).nitro || {})
const nitroDevContext = getNitroContext(nuxt.options, { preset: 'dev' }) const nitroDevContext = getNitroContext(nuxt.options, { preset: 'dev' })
nuxt.server = createDevServer(nitroDevContext) nuxt.server = createDevServer(nitroDevContext)
// Connect hooks // Connect hooks
nuxt.addHooks(nitroContext.nuxtHooks) nuxt.hooks.addHooks(nitroContext.nuxtHooks)
nuxt.hook('close', () => nitroContext._internal.hooks.callHook('close')) nuxt.hook('close', () => nitroContext._internal.hooks.callHook('close'))
nuxt.addHooks(nitroDevContext.nuxtHooks) nuxt.hooks.addHooks(nitroDevContext.nuxtHooks)
nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close')) nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close'))
// Expose process.env.NITRO_PRESET // Expose process.env.NITRO_PRESET
@ -21,8 +20,7 @@ export function initNitro (nuxt: Nuxt) {
// Resolve middleware // Resolve middleware
nuxt.hook('modules:done', () => { nuxt.hook('modules:done', () => {
const { middleware, legacyMiddleware } = const { middleware, legacyMiddleware } = resolveMiddleware(nuxt.options.serverMiddleware)
resolveMiddleware(nuxt.options.serverMiddleware, nuxt.resolver.resolvePath)
nuxt.server.setLegacyMiddleware(legacyMiddleware) nuxt.server.setLegacyMiddleware(legacyMiddleware)
nitroContext.middleware.push(...middleware) nitroContext.middleware.push(...middleware)
nitroDevContext.middleware.push(...middleware) nitroDevContext.middleware.push(...middleware)
@ -39,7 +37,7 @@ export function initNitro (nuxt: Nuxt) {
} }
}) })
// nude dev // nuxt dev
if (nuxt.options.dev) { if (nuxt.options.dev) {
nitroDevContext._internal.hooks.hook('nitro:compiled', () => { nuxt.server.watch() }) nitroDevContext._internal.hooks.hook('nitro:compiled', () => { nuxt.server.watch() })
nuxt.hook('build:compile', ({ compiler }) => { compiler.outputFileSystem = wpfs }) nuxt.hook('build:compile', ({ compiler }) => { compiler.outputFileSystem = wpfs })

View File

@ -1,107 +1,64 @@
import type { IncomingHttpHeaders } from 'http'
import { dirname } from 'path' import { dirname } from 'path'
import isPlainObject from 'lodash/isPlainObject'
import consola from 'consola'
import Hookable from 'hookable' import Hookable from 'hookable'
import { LoadNuxtOptions, loadNuxtConfig } from '@nuxt/kit' import { loadNuxtConfig, LoadNuxtConfigOptions, Nuxt, NuxtOptions, installModule } from '@nuxt/kit'
import { version } from '../package.json'
import ModuleContainer from './module'
import Resolver from './resolver'
import { initNitro } from './nitro' import { initNitro } from './nitro'
declare global { export function createNuxt (options: NuxtOptions): Nuxt {
namespace NodeJS { const hooks = new Hookable()
interface Global {
__NUXT_DEV__: boolean return {
} options,
hooks,
callHook: hooks.callHook,
hook: hooks.hook
} }
} }
export class Nuxt extends Hookable { async function initNuxt (nuxt: Nuxt) {
_ready?: Promise<this> // Register user hooks
_initCalled?: boolean nuxt.hooks.addHooks(nuxt.options.hooks)
error?: Error & { statusCode?: number, headers?: IncomingHttpHeaders } // Init nitro
options: any await initNitro(nuxt)
resolver: Resolver
moduleContainer: ModuleContainer
server?: any
renderer?: any
render?: any['app']
showReady?: () => void
constructor (options) { // Init user modules
super(consola) await nuxt.callHook('modules:before', nuxt)
const modulesToInstall = [
...nuxt.options.buildModules,
...nuxt.options.modules,
...nuxt.options._modules
]
this.options = options for (const m of modulesToInstall) {
await installModule(nuxt, m)
// Create instance of core components
this.resolver = new Resolver(this)
this.moduleContainer = new ModuleContainer(this)
// Call ready
if (this.options._ready !== false) {
this.ready().catch((err) => {
consola.fatal(err)
})
}
} }
static get version () { await nuxt.callHook('modules:done', nuxt)
return `v${version}` + (global.__NUXT_DEV__ ? '-development' : '')
}
ready () { await nuxt.callHook('ready', nuxt)
if (!this._ready) {
this._ready = this._init()
}
return this._ready
}
async _init () {
if (this._initCalled) {
return this
}
this._initCalled = true
// Add hooks
if (this.options.hooks instanceof Function) {
this.options.hooks(this.hook)
} else if (isPlainObject(this.options.hooks)) {
this.addHooks(this.options.hooks)
}
// Await for server
await initNitro(this)
// Await for modules
await this.moduleContainer.ready()
// Call ready hook
await this.callHook('ready', this)
return this
}
async close (callback?: () => any | Promise<any>) {
await this.callHook('close', this)
if (typeof callback === 'function') {
await callback()
}
}
} }
export async function loadNuxt (opts: LoadNuxtOptions) { export interface LoadNuxtOptions extends LoadNuxtConfigOptions {
const options = await loadNuxtConfig(opts) for?: 'dev' | 'build'
}
export async function loadNuxt (loadOpts: LoadNuxtOptions = {}): Promise<Nuxt> {
const options = loadNuxtConfig({
config: {
dev: loadOpts.for === 'dev',
...loadOpts.config
},
...loadOpts
})
// Temp // Temp
// @ts-ignore
options.appDir = dirname(require.resolve('@nuxt/app')) options.appDir = dirname(require.resolve('@nuxt/app'))
options._majorVersion = 3 options._majorVersion = 3
const nuxt = new Nuxt(options) const nuxt = createNuxt(options)
await nuxt.ready()
await initNuxt(nuxt)
return nuxt return nuxt
} }

View File

@ -32,7 +32,7 @@ interface SegmentToken {
export async function resolvePagesRoutes (builder: Builder, app: NuxtApp) { export async function resolvePagesRoutes (builder: Builder, app: NuxtApp) {
const pagesDir = resolve(app.dir, app.pages!.dir) const pagesDir = resolve(app.dir, app.pages!.dir)
const pagesPattern = `${app.pages!.dir}/**/*.{${app.extensions.join(',')}}` const pagesPattern = `${app.pages!.dir}/**/*{${app.extensions.join(',')}}`
const files = await resolveFiles(builder, pagesPattern, app.dir) const files = await resolveFiles(builder, pagesPattern, app.dir)
// Sort to make sure parent are listed first // Sort to make sure parent are listed first

View File

@ -1,186 +0,0 @@
import { resolve, join } from 'path'
import fs from 'fs-extra'
import jiti from 'jiti'
import {
startsWithRootAlias,
startsWithSrcAlias,
isExternalDependency,
clearRequireCache
} from '@nuxt/kit'
import { Nuxt } from './nuxt'
interface ResolvePathOptions {
isAlias?: boolean
isModule?: boolean
isStyle?: boolean
}
interface RequireModuleOptions {
useESM?: boolean
isAlias?: boolean
interopDefault?: any
}
export default class Resolver {
_require: NodeJS.Require
_resolve: NodeJS.RequireResolve
nuxt: Nuxt
options: Nuxt['options']
constructor (nuxt: Nuxt) {
this.nuxt = nuxt
this.options = this.nuxt.options
// Binds
this.resolvePath = this.resolvePath.bind(this)
this.resolveAlias = this.resolveAlias.bind(this)
this.resolveModule = this.resolveModule.bind(this)
this.requireModule = this.requireModule.bind(this)
this._require = jiti(__filename)
this._resolve = this._require.resolve
}
resolveModule (path: string) {
try {
return this._resolve(path, {
paths: this.options.modulesDir
})
} catch (error) {
if (error.code !== 'MODULE_NOT_FOUND') {
// TODO: remove after https://github.com/facebook/jest/pull/8487 released
if (process.env.NODE_ENV === 'test' && error.message.startsWith('Cannot resolve module')) {
return
}
throw error
}
}
}
resolveAlias (path: string) {
if (startsWithRootAlias(path)) {
return join(this.options.rootDir, path.substr(2))
}
if (startsWithSrcAlias(path)) {
return join(this.options.srcDir, path.substr(1))
}
return resolve(this.options.srcDir, path)
}
resolvePath (path: string, { isAlias, isModule, isStyle }: ResolvePathOptions = {}) {
// Fast return in case of path exists
if (fs.existsSync(path)) {
return path
}
let resolvedPath: string
// Try to resolve it as a regular module
if (isModule !== false) {
resolvedPath = this.resolveModule(path)
}
// Try to resolve alias
if (!resolvedPath && isAlias !== false) {
resolvedPath = this.resolveAlias(path)
}
// Use path for resolvedPath
if (!resolvedPath) {
resolvedPath = path
}
let isDirectory: boolean
// Check if resolvedPath exits and is not a directory
if (fs.existsSync(resolvedPath)) {
isDirectory = fs.lstatSync(resolvedPath).isDirectory()
if (!isDirectory) {
return resolvedPath
}
}
const extensions = isStyle ? this.options.styleExtensions : this.options.extensions
// Check if any resolvedPath.[ext] or resolvedPath/index.[ext] exists
for (const ext of extensions) {
if (!isDirectory && fs.existsSync(resolvedPath + '.' + ext)) {
return resolvedPath + '.' + ext
}
const resolvedPathwithIndex = join(resolvedPath, 'index.' + ext)
if (isDirectory && fs.existsSync(resolvedPathwithIndex)) {
return resolvedPathwithIndex
}
}
// If there's no index.[ext] we just return the directory path
if (isDirectory) {
return resolvedPath
}
// Give up
throw new Error(`Cannot resolve "${path}" from "${resolvedPath}"`)
}
tryResolvePath (path: string, options?: ResolvePathOptions) {
try {
return this.resolvePath(path, options)
} catch (e) {
}
}
requireModule <T> (path: string, { useESM, isAlias, interopDefault }: RequireModuleOptions = {}): T {
let resolvedPath = path
let requiredModule: any
let lastError: any
// Try to resolve path
try {
resolvedPath = this.resolvePath(path, { isAlias })
} catch (e) {
lastError = e
}
const isExternal = isExternalDependency(resolvedPath)
// in dev mode make sure to clear the require cache so after
// a dev server restart any changed file is reloaded
if (this.options.dev && !isExternal) {
clearRequireCache(resolvedPath)
}
// By default use esm only for js,mjs files outside of node_modules
if (useESM === undefined) {
useESM = !isExternal && /.(js|mjs)$/.test(resolvedPath)
}
// Try to require
try {
if (useESM) {
requiredModule = this._require(resolvedPath)
} else {
requiredModule = require(resolvedPath)
}
} catch (e) {
lastError = e
}
// Interop default
if (interopDefault !== false && requiredModule && requiredModule.default) {
requiredModule = requiredModule.default
}
// Throw error if failed to require
if (requiredModule === undefined && lastError) {
throw lastError
}
return requiredModule
}
}

View File

@ -19,10 +19,16 @@ export function templateData (builder) {
} }
} }
async function compileTemplate ({ src, path, data }: NuxtTemplate, destDir: string) { async function compileTemplate (tmpl: NuxtTemplate, destDir: string) {
const srcContents = await fsExtra.readFile(src, 'utf-8') const srcContents = await fsExtra.readFile(tmpl.src, 'utf-8')
const compiledSrc = lodashTemplate(srcContents, {})(data) let compiledSrc
const dest = join(destDir, path) try {
compiledSrc = lodashTemplate(srcContents, {})(tmpl.data)
} catch (err) {
console.error('Error compiling template: ', tmpl)
throw err
}
const dest = join(destDir, tmpl.path)
// consola.log('Compile template', dest) // consola.log('Compile template', dest)
await fsExtra.mkdirp(dirname(dest)) await fsExtra.mkdirp(dirname(dest))
await fsExtra.writeFile(dest, compiledSrc) await fsExtra.writeFile(dest, compiledSrc)

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import consola from 'consola' import consola from 'consola'
@ -5,10 +6,9 @@ import defaults from 'lodash/defaults'
import merge from 'lodash/merge' import merge from 'lodash/merge'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import createResolver from 'postcss-import-resolver' import createResolver from 'postcss-import-resolver'
import { Nuxt, tryRequireModule } from '@nuxt/kit'
import { isPureObject } from '@nuxt/kit' const isPureObject = obj => obj !== null && !Array.isArray(obj) && typeof obj === 'object'
import type { Nuxt } from '../../../core'
import type { NormalizedConfiguration } from '../../../config'
export const orderPresets = { export const orderPresets = {
cssnanoLast (names) { cssnanoLast (names) {
@ -30,17 +30,18 @@ export const orderPresets = {
} }
} }
let _postcssConfigFileWarningShown
function postcssConfigFileWarning () { function postcssConfigFileWarning () {
if (postcssConfigFileWarning.executed) { if (_postcssConfigFileWarningShown) {
return return
} }
consola.warn('Please use `build.postcss` in your nuxt.config.js instead of an external config file. Support for such files will be removed in Nuxt 3 as they remove all defaults set by Nuxt and can cause severe problems with features like alias resolving inside your CSS.') consola.warn('Please use `build.postcss` in your nuxt.config.js instead of an external config file. Support for such files will be removed in Nuxt 3 as they remove all defaults set by Nuxt and can cause severe problems with features like alias resolving inside your CSS.')
postcssConfigFileWarning.executed = true _postcssConfigFileWarningShown = true
} }
export default class PostcssConfig { export default class PostcssConfig {
nuxt: Nuxt nuxt: Nuxt
options: NormalizedConfiguration options: Nuxt['options']
constructor (nuxt) { constructor (nuxt) {
this.nuxt = nuxt this.nuxt = nuxt
@ -150,7 +151,7 @@ export default class PostcssConfig {
// Map postcss plugins into instances on object mode once // Map postcss plugins into instances on object mode once
config.plugins = this.sortPlugins(config) config.plugins = this.sortPlugins(config)
.map((p) => { .map((p) => {
const plugin = this.nuxt.resolver.requireModule(p) const plugin = tryRequireModule(p)
const opts = plugins[p] const opts = plugins[p]
if (opts === false) { if (opts === false) {
return false // Disabled return false // Disabled

View File

@ -1,3 +1,5 @@
export default { import { defineNuxtConfig } from '@nuxt/kit'
export default defineNuxtConfig({
vite: true vite: true
} })

View File

@ -1,7 +1,7 @@
import { promisify } from 'util' import { promisify } from 'util'
import { resolve, relative, dirname } from 'path' import { resolve, relative, dirname } from 'path'
import Module from 'module' import Module from 'module'
import { writeFile, mkdir } from 'fs/promises' import { writeFile, mkdir, unlink, symlink } from 'fs/promises'
import chalk from 'chalk' import chalk from 'chalk'
import consola from 'consola' import consola from 'consola'
import rimraf from 'rimraf' import rimraf from 'rimraf'
@ -12,12 +12,16 @@ import alias from '@rollup/plugin-alias'
import esbuild from 'rollup-plugin-esbuild' import esbuild from 'rollup-plugin-esbuild'
import { mkdist } from 'mkdist' import { mkdist } from 'mkdist'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import execa from 'execa'
import dts from 'rollup-plugin-dts'
interface BuildEntry { interface BuildEntry {
name: string name: string
input: string input: string
output: string output: string
bundle: boolean bundle: boolean
srcDir: string
distDir: string
format: 'esm' | 'cjs' format: 'esm' | 'cjs'
} }
@ -51,7 +55,14 @@ async function main () {
ctx.externals.push(...buildOptions.externals) ctx.externals.push(...buildOptions.externals)
} }
await promisify(rimraf)(resolve(ctx.rootDir, 'dist')) const distDir = resolve(ctx.rootDir, 'dist')
await unlink(distDir).catch(() => {})
await promisify(rimraf)(distDir)
if (buildOptions.prebuild) {
const [cmd, ...args] = buildOptions.prebuild.split(' ')
await execa(cmd, args)
}
if (args.includes('--stub')) { if (args.includes('--stub')) {
const stubbed: string[] = [] const stubbed: string[] = []
@ -65,40 +76,50 @@ async function main () {
const esStub = `export * from '${input}'` const esStub = `export * from '${input}'`
await writeFile(output, entry.format === 'cjs' ? cjsStub : esStub) await writeFile(output, entry.format === 'cjs' ? cjsStub : esStub)
await writeFile(output.replace('.js', '.d.ts'), esStub) await writeFile(output.replace('.js', '.d.ts'), esStub)
} else {
const outDir = resolve(ctx.rootDir, entry.output)
const srcDir = resolve(ctx.rootDir, entry.input)
await unlink(outDir).catch(() => { })
await symlink(srcDir, outDir)
} }
} }
consola.success(`Stub done: ${stubbed.join(', ')}`)
return return
} }
consola.info(chalk.cyan(`Builduing ${pkg.name}`))
if (process.env.DEBUG) { if (process.env.DEBUG) {
consola.info(`${chalk.cyan(`Builduing ${pkg.name}`)} consola.info(`
${chalk.bold('Root dir:')} ${ctx.rootDir}
${chalk.bold('Root dir:')} ${ctx.rootDir} ${chalk.bold('Entries:')}
${chalk.bold('Entries:')} ${ctx.entries.map(entry => ' ' + dumpObject(entry)).join('\n')}
${ctx.entries.map(entry => ' ' + dumpObject(entry)).join('\n')}
`) `)
} }
const rollupOptions = getRollupOptions(ctx) const rollupOptions = getRollupOptions(ctx)
const buildResult = await rollup(rollupOptions)
const outputOptions = rollupOptions.output as OutputOptions
const { output } = await buildResult.write(outputOptions)
const usedImports = new Set<string>()
const buildEntries: { path: string, bytes?: number, exports?: string[], chunks?: string[] }[] = [] const buildEntries: { path: string, bytes?: number, exports?: string[], chunks?: string[] }[] = []
const usedImports = new Set<string>()
if (rollupOptions) {
const buildResult = await rollup(rollupOptions)
const outputOptions = rollupOptions.output as OutputOptions
const { output } = await buildResult.write(outputOptions)
for (const entry of output.filter(e => e.type === 'chunk') as OutputChunk[]) { for (const entry of output.filter(e => e.type === 'chunk') as OutputChunk[]) {
for (const id of entry.imports) { for (const id of entry.imports) {
usedImports.add(id) usedImports.add(id)
} }
if (entry.isEntry) { if (entry.isEntry) {
buildEntries.push({ buildEntries.push({
path: entry.fileName, path: relative(ctx.rootDir, resolve(outputOptions.dir, entry.fileName)),
bytes: entry.code.length * 4, bytes: entry.code.length * 4,
exports: entry.exports exports: entry.exports
}) })
}
} }
// Types
rollupOptions.plugins.push(dts())
const typesBuild = await rollup(rollupOptions)
await typesBuild.write(outputOptions)
} }
for (const entry of ctx.entries.filter(e => !e.bundle)) { for (const entry of ctx.entries.filter(e => !e.bundle)) {
@ -109,50 +130,51 @@ ${ctx.entries.map(entry => ' ' + dumpObject(entry)).join('\n')}
format: entry.format format: entry.format
}) })
buildEntries.push({ buildEntries.push({
path: entry.output, path: relative(ctx.rootDir, entry.output),
bytes: 0, chunks: [`${writtenFiles.length} files`]
chunks: writtenFiles.map(p => relative(resolve(ctx.rootDir, entry.output), p))
}) })
} }
consola.success(chalk.green('Build succeed')) consola.success(chalk.green('Build succeed for ' + pkg.name))
for (const entry of buildEntries) {
if (process.env.DEBUG) { consola.log(` ${chalk.bold(entry.path)} (` + [
consola.log(` entry.bytes && `size: ${chalk.cyan(prettyBytes(entry.bytes))}`,
${buildEntries.map(entry => `${chalk.bold(entry.path)} entry.exports && `exports: ${chalk.gray(entry.exports.join(', '))}`,
size: ${chalk.cyan(entry.bytes ? prettyBytes(entry.bytes) : '-')} entry.chunks && `chunks: ${chalk.gray(entry.chunks.join(', '))}`
exports: ${chalk.gray(entry.exports ? entry.exports.join(', ') : '-')} ].filter(Boolean).join(', ') + ')')
chunks: ${chalk.gray(entry.chunks ? entry.chunks.join(', ') : '-')}`
).join('\n')}`)
} }
const usedDependencies = new Set<string>() if (rollupOptions) {
const unusedDependencies = new Set<string>(Object.keys(pkg.dependencies || {})) const usedDependencies = new Set<string>()
const implicitDependnecies = new Set<string>() const unusedDependencies = new Set<string>(Object.keys(pkg.dependencies || {}))
for (const id of usedImports) { const implicitDependnecies = new Set<string>()
unusedDependencies.delete(id) for (const id of usedImports) {
usedDependencies.add(id)
}
if (Array.isArray(buildOptions.dependencies)) {
for (const id of buildOptions.dependencies) {
unusedDependencies.delete(id) unusedDependencies.delete(id)
usedDependencies.add(id)
}
if (Array.isArray(buildOptions.dependencies)) {
for (const id of buildOptions.dependencies) {
unusedDependencies.delete(id)
}
}
for (const id of usedDependencies) {
if (
!ctx.externals.includes(id) &&
!id.startsWith('chunks/') &&
!ctx.externals.includes(id.split('/')[0]) // lodash/get
) {
implicitDependnecies.add(id)
}
}
if (unusedDependencies.size) {
consola.warn('Potential unused dependencies found:', Array.from(unusedDependencies).map(id => chalk.cyan(id)).join(', '))
}
if (implicitDependnecies.size) {
consola.warn('Potential implicit dependencies found:', Array.from(implicitDependnecies).map(id => chalk.cyan(id)).join(', '))
} }
} }
for (const id of usedDependencies) {
if ( consola.log('')
!ctx.externals.includes(id) &&
!id.startsWith('chunks/') &&
!ctx.externals.includes(id.split('/')[0]) // lodash/get
) {
implicitDependnecies.add(id)
}
}
if (unusedDependencies.size) {
consola.warn('Potential unused dependencies found:', Array.from(unusedDependencies).map(id => chalk.cyan(id)).join(', '))
}
if (implicitDependnecies.size) {
consola.warn('Potential implicit dependencies found:', Array.from(implicitDependnecies).map(id => chalk.cyan(id)).join(', '))
}
} }
function resolveEntry (input: string | [string, Partial<BuildEntry>] | Partial<BuildEntry>): BuildEntry { function resolveEntry (input: string | [string, Partial<BuildEntry>] | Partial<BuildEntry>): BuildEntry {
@ -163,9 +185,9 @@ function resolveEntry (input: string | [string, Partial<BuildEntry>] | Partial<B
if (Array.isArray(input)) { if (Array.isArray(input)) {
entry = { name: input[0], ...input[1] } entry = { name: input[0], ...input[1] }
} }
entry.input = entry.input ?? `src/${entry.name}` entry.input = entry.input ?? resolve(entry.srcDir || 'src', './' + entry.name)
entry.output = entry.output ?? `dist/${entry.name}` entry.output = entry.output ?? resolve(entry.distDir || 'dist', './' + entry.name)
entry.bundle = entry.bundle ?? !entry.input.endsWith('/') entry.bundle = entry.bundle ?? !(entry.input.endsWith('/') || entry.name.endsWith('/'))
entry.format = entry.format ?? 'esm' entry.format = entry.format ?? 'esm'
return entry as BuildEntry return entry as BuildEntry
} }
@ -174,13 +196,18 @@ function dumpObject (obj) {
return '{ ' + Object.keys(obj).map(key => `${key}: ${JSON.stringify(obj[key])}`).join(', ') + ' }' return '{ ' + Object.keys(obj).map(key => `${key}: ${JSON.stringify(obj[key])}`).join(', ') + ' }'
} }
function getRollupOptions (ctx: BuildContext): RollupOptions { function getRollupOptions (ctx: BuildContext): RollupOptions | null {
const extensions = ['.ts', '.mjs', '.js', '.json'] const extensions = ['.ts', '.mjs', '.js', '.json']
const r = (...path) => resolve(ctx.rootDir, ...path) const r = (...path) => resolve(ctx.rootDir, ...path)
const entries = ctx.entries.filter(e => e.bundle)
if (!entries.length) {
return null
}
return <RollupOptions>{ return <RollupOptions>{
input: ctx.entries.filter(e => e.bundle).map(e => e.input), input: entries.map(e => e.input),
output: { output: {
dir: r('dist'), dir: r('dist'),
@ -194,7 +221,17 @@ function getRollupOptions (ctx: BuildContext): RollupOptions {
if (id[0] === '.' || id[0] === '/' || id.includes('src/')) { if (id[0] === '.' || id[0] === '/' || id.includes('src/')) {
return false return false
} }
return !!ctx.externals.find(ext => id.includes(ext)) const isExplicitExternal = !!ctx.externals.find(ext => id.includes(ext))
if (!isExplicitExternal) {
consola.warn(`Inlining external ${id}`)
}
return isExplicitExternal
},
onwarn (warning, rollupWarn) {
if (!['CIRCULAR_DEPENDENCY'].includes(warning.code)) {
rollupWarn(warning)
}
}, },
plugins: [ plugins: [
@ -222,4 +259,7 @@ function getRollupOptions (ctx: BuildContext): RollupOptions {
} }
} }
main().catch(consola.error) main().catch((err) => {
consola.error(err)
process.exit(1)
})

View File

@ -2196,6 +2196,17 @@
"@vue/babel-plugin-transform-vue-jsx" "^1.2.1" "@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
camelcase "^5.0.0" camelcase "^5.0.0"
"@vue/compiler-core@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.10.tgz#ced92120c6b9bab7b6c44dfe5e3e5cf2ea422531"
integrity sha512-rayD+aODgX9CWgWv0cAI+whPLyMmtkWfNGsZpdpsaIloh8mY2hX8+SvE1Nn3755YhGWJ/7oaDEcNpOctGwZbsA==
dependencies:
"@babel/parser" "^7.12.0"
"@babel/types" "^7.12.0"
"@vue/shared" "3.0.10"
estree-walker "^2.0.1"
source-map "^0.6.1"
"@vue/compiler-core@3.0.9": "@vue/compiler-core@3.0.9":
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.9.tgz#ec7efa676889aee006fc43739ee4a67a952ac623" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.9.tgz#ec7efa676889aee006fc43739ee4a67a952ac623"
@ -2207,6 +2218,14 @@
estree-walker "^2.0.1" estree-walker "^2.0.1"
source-map "^0.6.1" source-map "^0.6.1"
"@vue/compiler-dom@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.10.tgz#59d3597498e7d4b0b92f3886a823f99d5b08f1fe"
integrity sha512-SzN1li9xAxtqkZimR1AFU2t1N0vzsAJxR/5764xoS0xedwhUU9s8s+Tks2FNMLsXiqdkP2Qd4zAM+9EwTbZmRw==
dependencies:
"@vue/compiler-core" "3.0.10"
"@vue/shared" "3.0.10"
"@vue/compiler-dom@3.0.9": "@vue/compiler-dom@3.0.9":
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.9.tgz#1fd554097d9ab36eca73bc6d0d9607fecf94e71c" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.9.tgz#1fd554097d9ab36eca73bc6d0d9607fecf94e71c"
@ -2245,6 +2264,13 @@
"@vue/compiler-dom" "3.0.9" "@vue/compiler-dom" "3.0.9"
"@vue/shared" "3.0.9" "@vue/shared" "3.0.9"
"@vue/reactivity@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.10.tgz#012830733291e60827f3b228d425ad53b83484ce"
integrity sha512-0GOSqlIv/a5wy4r6fAcdaglQ8v2sLYMRUpu49yK8Z2vHccK85Ym3R9C9K3vo6dfBRGbbCVvoKxYtQw49LvE8Ug==
dependencies:
"@vue/shared" "3.0.10"
"@vue/reactivity@3.0.9": "@vue/reactivity@3.0.9":
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.9.tgz#875f241b8c10262560b190ccdeff2d0ab7053e11" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.9.tgz#875f241b8c10262560b190ccdeff2d0ab7053e11"
@ -2252,6 +2278,14 @@
dependencies: dependencies:
"@vue/shared" "3.0.9" "@vue/shared" "3.0.9"
"@vue/runtime-core@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.10.tgz#cb8730c0ec86ea5c1cfa701facc0a97bf59b15a2"
integrity sha512-qKhCOwHGff5YEdyClO1gf9Q9xgaPPz/qJ2GyzNZkPb00WcXJ3l+yTgHZWaSywRLs9GD1y9Ff3C0MIowzj95NHA==
dependencies:
"@vue/reactivity" "3.0.10"
"@vue/shared" "3.0.10"
"@vue/runtime-core@3.0.9": "@vue/runtime-core@3.0.9":
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.9.tgz#9665f149468355a524a304cb8f260147a4d294e6" resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.9.tgz#9665f149468355a524a304cb8f260147a4d294e6"
@ -2260,6 +2294,15 @@
"@vue/reactivity" "3.0.9" "@vue/reactivity" "3.0.9"
"@vue/shared" "3.0.9" "@vue/shared" "3.0.9"
"@vue/runtime-dom@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.10.tgz#80c6ee28caeabf74f31357d2c64d177945bd8a5f"
integrity sha512-8yRAALc/884UlYWY7hJImecvow1Cngbl2B6n0ThYTms08FVQ3W9tdW0MEvR3JVit06JyQLS1Qvwdn1PwNPPDqg==
dependencies:
"@vue/runtime-core" "3.0.10"
"@vue/shared" "3.0.10"
csstype "^2.6.8"
"@vue/runtime-dom@3.0.9": "@vue/runtime-dom@3.0.9":
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.9.tgz#16a1d001dc746a9f346ee7fb9de90d52ad097b61" resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.9.tgz#16a1d001dc746a9f346ee7fb9de90d52ad097b61"
@ -2277,6 +2320,11 @@
"@vue/compiler-ssr" "3.0.9" "@vue/compiler-ssr" "3.0.9"
"@vue/shared" "3.0.9" "@vue/shared" "3.0.9"
"@vue/shared@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.10.tgz#5476d5615d01bf339c65c2e804f5909bbc27844a"
integrity sha512-p8GJ+bGpEGiEHICwcCH/EtJnkZQllrOfm1J2J+Ep0ydMte25bPnArgrY/h2Tn1LKqqR3LXyQlOSYY6gJgiW2LQ==
"@vue/shared@3.0.9": "@vue/shared@3.0.9":
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.9.tgz#09882d745ded52b07e4481d036659d733edd2a9a" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.9.tgz#09882d745ded52b07e4481d036659d733edd2a9a"
@ -7544,10 +7592,10 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
dependencies: dependencies:
minimist "^1.2.5" minimist "^1.2.5"
mkdist@^0.1.2: mkdist@^0.1.3:
version "0.1.2" version "0.1.3"
resolved "https://registry.yarnpkg.com/mkdist/-/mkdist-0.1.2.tgz#7365d975005976a7ac22540c0e76f8b21b33cdb0" resolved "https://registry.yarnpkg.com/mkdist/-/mkdist-0.1.3.tgz#46787c2493493d8eff1770700da23d5c4fc3d844"
integrity sha512-NELp7Nnz8pKek6pvIz4snM1j+D8ni9Gm5UhKApuK9pgrbd/Q3KveVg2HtEWy73AKuG1wXnK9O3cjfKD5OgG9Ww== integrity sha512-WQ0l+v0ICvxvsmgi2MtePzeyis5kMDRUMnibUDsc1NdVSo+lsoiIYbLrvIBgKJGJ2ITMaLDtCAHiFjF3U7h29A==
dependencies: dependencies:
defu "^3.2.2" defu "^3.2.2"
esbuild "^0.8.56" esbuild "^0.8.56"
@ -9970,13 +10018,6 @@ static-extend@^0.1.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
std-env@3.0.0-alpha:
version "3.0.0-alpha"
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.0.0-alpha.tgz#f161964da20a8af6a11bdbccfd39a99b5d7728d1"
integrity sha512-WWaDtGMLaG1AqB8n792lM8zfR38xVw8iFIFGBvQRV4L+4kps54gvluqKcOq2Do2FwXNoKKjUJHRL1xgnz2pN3A==
dependencies:
ci-info "^2.0.0"
std-env@^2.2.1, std-env@^2.3.0: std-env@^2.2.1, std-env@^2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/std-env/-/std-env-2.3.0.tgz#66d4a4a4d5224242ed8e43f5d65cfa9095216eee" resolved "https://registry.yarnpkg.com/std-env/-/std-env-2.3.0.tgz#66d4a4a4d5224242ed8e43f5d65cfa9095216eee"
@ -10634,6 +10675,11 @@ unbox-primitive@^1.0.0:
has-symbols "^1.0.0" has-symbols "^1.0.0"
which-boxed-primitive "^1.0.1" which-boxed-primitive "^1.0.1"
unctx@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/unctx/-/unctx-0.0.3.tgz#e06f67d5bebe2babe5a57c1dc13d77255e3458c0"
integrity sha512-x+NCoXiYn93laQNnoJGZx2UZj7vv8ViFKadUCDx9S4QoPIkGRCYT0OLUDEMlg/B+Q6bnqdSkPLmiy/kjNIwVyQ==
union-value@^1.0.0: union-value@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@ -10904,6 +10950,15 @@ vue-template-compiler@^2.6.12:
de-indent "^1.0.2" de-indent "^1.0.2"
he "^1.1.0" he "^1.1.0"
vue@^3.0.10:
version "3.0.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.10.tgz#b5d2801c6ac0e756c850ad7a8f9a78cbccbad02a"
integrity sha512-6arZ722uqIArSNUU94aqx0Pq0IMHFqYZuJ+U+q9HGdZZu11VFpyFP/L/hakijGFKp56Jr0yxJdWbDiJGWPxwww==
dependencies:
"@vue/compiler-dom" "3.0.10"
"@vue/runtime-dom" "3.0.10"
"@vue/shared" "3.0.10"
vue@^3.0.9: vue@^3.0.9:
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.9.tgz#c68ffc0e4aa2b0f1905124a9037b6e352de469ad" resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.9.tgz#c68ffc0e4aa2b0f1905124a9037b6e352de469ad"