diff --git a/packages/saber/src/index.ts b/packages/saber/src/index.ts index bd08d6c4f..bb8403193 100644 --- a/packages/saber/src/index.ts +++ b/packages/saber/src/index.ts @@ -235,7 +235,7 @@ export class Saber { } configDir?: string configPath?: string - theme?: string + theme: string actualServerPort?: number constructor(opts: SaberConstructorOptions = {}, config: SaberConfig = {}) { @@ -299,8 +299,6 @@ export class Saber { this.configDir = '' this.configPath = '' - this.renderer = new VueRenderer() - // Load Saber config const loadedConfig = this.loadConfig() this.config = loadedConfig.config @@ -313,6 +311,30 @@ export class Saber { )}` ) } + + this.renderer = new VueRenderer() + this.renderer.init(this) + + // Load theme + if (this.config.theme) { + this.theme = resolvePackage(this.config.theme, { + cwd: this.configDir || this.opts.cwd, + prefix: 'saber-theme-' + }) + // When a theme is loaded from `node_modules` and `$theme/dist` directory exists + // We use the `dist` directory instead + if (this.theme.includes('node_modules')) { + const distDir = path.join(this.theme, 'dist') + if (fs.existsSync(distDir)) { + this.theme = distDir + } + } + + log.info(`Using theme: ${colors.dim(this.config.theme)}`) + log.verbose(() => `Theme directory: ${colors.dim(this.theme)}`) + } else { + this.theme = this.renderer.defaultTheme + } } loadConfig(configFiles = configLoader.CONFIG_FILES) { @@ -343,29 +365,6 @@ export class Saber { } async prepare() { - this.renderer.init(this) - - // Load theme - if (this.config.theme) { - this.theme = resolvePackage(this.config.theme, { - cwd: this.configDir || this.opts.cwd, - prefix: 'saber-theme-' - }) - // When a theme is loaded from `node_modules` and `$theme/dist` directory exists - // We use the `dist` directory instead - if (this.theme.includes('node_modules')) { - const distDir = path.join(this.theme, 'dist') - if (fs.existsSync(distDir)) { - this.theme = distDir - } - } - - log.info(`Using theme: ${colors.dim(this.config.theme)}`) - log.verbose(() => `Theme directory: ${colors.dim(this.theme)}`) - } else { - this.theme = this.renderer.defaultTheme - } - // Load built-in plugins for (const plugin of builtinPlugins) { const resolvedPlugin = require(plugin.resolve) diff --git a/packages/saber/src/plugins/layouts.js b/packages/saber/src/plugins/layouts.js deleted file mode 100644 index 0853f3b65..000000000 --- a/packages/saber/src/plugins/layouts.js +++ /dev/null @@ -1,113 +0,0 @@ -const path = require('path') -const { glob, fs, slash } = require('saber-utils') - -const ID = 'builtin:layouts' - -exports.name = ID - -exports.apply = api => { - const setLayout = (layouts, filepath, shouldDelete) => { - const layoutName = path.basename(filepath, path.extname(filepath)) - if (shouldDelete) { - delete layouts[layoutName] - } else { - layouts[layoutName] = slash(filepath) - } - } - - const getLayouts = async dir => { - const files = await glob('*.{vue,js}', { - cwd: dir - }) - const layouts = {} - files.forEach(file => { - setLayout(layouts, path.join(dir, file)) - }) - return layouts - } - - const writeLayouts = async (themeLayouts, userLayouts) => { - const layouts = Object.assign({}, themeLayouts, userLayouts) - - const outFile = api.resolveCache('layouts.js') - const outContent = `var layouts = {} - - ${Object.keys(layouts) - .map((name, index) => { - return ` - import layout_${index} from "${layouts[name]}" - layouts["${name}"] = layout_${index} - ` - }) - .join('\n')} - - export default layouts` - await fs.outputFile(outFile, outContent, 'utf8') - } - - api.hooks.beforeRun.tapPromise(ID, async () => { - const themeLayoutsDir = path.join(api.theme, 'layouts') - const userLayoutsDir = api.resolveCwd('layouts') - const [themeLayouts, userLayouts] = await Promise.all([ - getLayouts(themeLayoutsDir), - getLayouts(userLayoutsDir) - ]) - await writeLayouts(themeLayouts, userLayouts) - - if (api.dev) { - const watchLayouts = (dir, layouts) => { - const chokidar = require('chokidar') - - const onRemoveDir = async dir => { - if (!dir) { - Object.keys(layouts).forEach(name => { - delete layouts[name] - }) - await writeLayouts(themeLayouts, userLayouts) - } - } - - const onAddLayout = async file => { - setLayout(layouts, path.join(dir, file)) - await writeLayouts(themeLayouts, userLayouts) - } - - const onRemoveLayout = async file => { - setLayout(layouts, path.join(dir, file), true) - await writeLayouts(themeLayouts, userLayouts) - } - - // Clear the layouts object when the layouts directory is removed - chokidar - .watch('.', { - cwd: dir, - disableGlobbing: true, - ignored(filepath) { - return filepath !== dir - }, - ignoreInitial: true - }) - .on('unlinkDir', dir => { - onRemoveDir(dir) - }) - - // Add/Remove layout components - chokidar - .watch('*.{vue,js}', { cwd: dir, ignoreInitial: true }) - .on('add', file => { - onAddLayout(file) - }) - .on('unlink', file => { - onRemoveLayout(file) - }) - } - - // No need to watch theme layouts if it's from an npm package - if (!themeLayoutsDir.includes('node_modules')) { - watchLayouts(themeLayoutsDir, themeLayouts) - } - - watchLayouts(userLayoutsDir, userLayouts) - } - }) -} diff --git a/packages/saber/src/plugins/layouts.ts b/packages/saber/src/plugins/layouts.ts new file mode 100644 index 000000000..5f7ea6535 --- /dev/null +++ b/packages/saber/src/plugins/layouts.ts @@ -0,0 +1,131 @@ +import path from 'path' +import { glob, fs, slash } from 'saber-utils' +import { SaberPlugin } from '..' + +interface Layouts { + [name: string]: string +} + +const ID = 'builtin:layouts' + +const layoutsPlugin: SaberPlugin = { + name: ID, + + apply(api) { + const setLayout = ( + layouts: Layouts, + filepath: string, + shouldDelete?: boolean + ) => { + const layoutName = path.basename(filepath, path.extname(filepath)) + if (shouldDelete) { + delete layouts[layoutName] + } else { + layouts[layoutName] = slash(filepath) + } + } + + const getLayouts = async (dir: string) => { + const files = await glob('*.{vue,js}', { + cwd: dir + }) + const layouts = {} + files.forEach(file => { + setLayout(layouts, path.join(dir, file as string)) + }) + return layouts + } + + const writeLayouts = async ( + themeLayouts: Layouts, + userLayouts: Layouts + ) => { + const layouts = Object.assign({}, themeLayouts, userLayouts) + + api.log.verbose(() => `Found layouts: ${Object.keys(layouts).join(', ')}`) + + const outFile = api.resolveCache('layouts.js') + const outContent = `var layouts = {} + + ${Object.keys(layouts) + .map((name, index) => { + return ` + import layout_${index} from "${layouts[name]}" + layouts["${name}"] = layout_${index} + ` + }) + .join('\n')} + + export default layouts` + await fs.outputFile(outFile, outContent, 'utf8') + } + + api.hooks.beforeRun.tapPromise(ID, async () => { + const themeLayoutsDir = path.join(api.theme, 'layouts') + const userLayoutsDir = api.resolveCwd('layouts') + const [themeLayouts, userLayouts] = await Promise.all([ + getLayouts(themeLayoutsDir), + getLayouts(userLayoutsDir) + ]) + await writeLayouts(themeLayouts, userLayouts) + + if (api.dev) { + const watchLayouts = (dir: string, layouts: Layouts) => { + const chokidar = require('chokidar') + + const onRemoveDir = async (dir: string) => { + if (!dir) { + Object.keys(layouts).forEach(name => { + delete layouts[name] + }) + await writeLayouts(themeLayouts, userLayouts) + } + } + + const onAddLayout = async (file: string) => { + setLayout(layouts, path.join(dir, file)) + await writeLayouts(themeLayouts, userLayouts) + } + + const onRemoveLayout = async (file: string) => { + setLayout(layouts, path.join(dir, file), true) + await writeLayouts(themeLayouts, userLayouts) + } + + // Clear the layouts object when the layouts directory is removed + chokidar + .watch('.', { + cwd: dir, + disableGlobbing: true, + ignored(filepath: string) { + return filepath !== dir + }, + ignoreInitial: true + }) + .on('unlinkDir', (dir: string) => { + onRemoveDir(dir) + }) + + // Add/Remove layout components + chokidar + .watch('*.{vue,js}', { cwd: dir, ignoreInitial: true }) + .on('add', (file: string) => { + onAddLayout(file) + }) + .on('unlink', (file: string) => { + onRemoveLayout(file) + }) + } + + // No need to watch theme layouts if it's from an npm package + if (!themeLayoutsDir.includes('node_modules')) { + watchLayouts(themeLayoutsDir, themeLayouts) + } + + watchLayouts(userLayoutsDir, userLayouts) + } + }) + } +} + +export default layoutsPlugin