diff --git a/other-packages/eslint-config-saber/index.js b/other-packages/eslint-config-saber/index.js index 2a1aee8d7..8191e3b73 100644 --- a/other-packages/eslint-config-saber/index.js +++ b/other-packages/eslint-config-saber/index.js @@ -36,6 +36,7 @@ module.exports = { requireLast: false } } - ] + ], + '@typescript-eslint/prefer-includes': 'off' } } diff --git a/packages/saber/src/Compiler.ts b/packages/saber/src/Compiler.ts index b939b1711..0ffdf4063 100644 --- a/packages/saber/src/Compiler.ts +++ b/packages/saber/src/Compiler.ts @@ -17,6 +17,7 @@ export class Compiler extends EventEmitter { injectToWebpack(config: WebpackChain) { const ID = `compiler-${this.type}` + // eslint-disable-next-line @typescript-eslint/no-this-alias const context = this config.plugin(ID).use( class { diff --git a/packages/saber/src/plugins/config-css.js b/packages/saber/src/plugins/config-css.js index 2d934785b..3d0e8f8be 100644 --- a/packages/saber/src/plugins/config-css.js +++ b/packages/saber/src/plugins/config-css.js @@ -150,7 +150,7 @@ exports.apply = api => { name: 'styles', // necessary to ensure async chunks are also extracted test: m => { - return /css\/mini-extract/.test(m.type) + return m.type && m.type.includes('css/mini-extract') }, chunks: 'all', enforce: true diff --git a/packages/saber/src/plugins/emit-runtime-polyfills.js b/packages/saber/src/plugins/emit-runtime-polyfills.js index a5289ef85..7a8d76dd8 100644 --- a/packages/saber/src/plugins/emit-runtime-polyfills.js +++ b/packages/saber/src/plugins/emit-runtime-polyfills.js @@ -18,6 +18,7 @@ exports.apply = api => { polyfills, 'utf8' ) + // eslint-disable-next-line require-atomic-updates previousPolyfills = polyfills } }) diff --git a/packages/saber/src/plugins/extend-browser-api.js b/packages/saber/src/plugins/extend-browser-api.js index 43ad66135..e608fd9d9 100644 --- a/packages/saber/src/plugins/extend-browser-api.js +++ b/packages/saber/src/plugins/extend-browser-api.js @@ -24,14 +24,20 @@ exports.apply = api => { cwd: api.resolveCwd(), ignoreInitial: true }) + const onAdd = async filename => { + api.browserApi.add(api.resolveCwd(filename)) + await api.browserApi.reload() + } + const onRemove = async filename => { + api.browserApi.delete(api.resolveCwd(filename)) + await api.browserApi.reload() + } watcher - .on('add', async filename => { - api.browserApi.add(api.resolveCwd(filename)) - await api.browserApi.reload() + .on('add', filename => { + onAdd(filename) }) - .on('unlink', async filename => { - api.browserApi.delete(api.resolveCwd(filename)) - await api.browserApi.reload() + .on('unlink', filename => { + onRemove(filename) }) } }) diff --git a/packages/saber/src/plugins/extend-node-api.js b/packages/saber/src/plugins/extend-node-api.js index cdd087045..38e16e20c 100644 --- a/packages/saber/src/plugins/extend-node-api.js +++ b/packages/saber/src/plugins/extend-node-api.js @@ -4,6 +4,10 @@ const { log, colors } = require('saber-log') const ID = 'builtin:extend-node-api' +function __noopHandler__(arg) { + return arg +} + exports.name = ID exports.apply = api => { @@ -58,35 +62,38 @@ exports.apply = api => { } }) - if (api.dev && !/node_modules/.test(nodeApiFile)) { + if (api.dev && !nodeApiFile.includes('node_modules')) { + const onChange = async action => { + updateNodeApi() + // Remove all child pages + api.pages.removeWhere(page => page.internal.parent) + await Promise.all( + [...api.pages.values()].map(async page => { + // Recreate the page + api.pages.createPage(page) + // A page has been created + await api.hooks.onCreatePage.promise(page) + }) + ) + // All pages are created + await api.hooks.onCreatePages.promise() + // Emit pages + await api.hooks.emitPages.promise() + // Emit route file + await api.hooks.emitRoutes.promise() + log.warn( + `${action[0].toUpperCase()}${action.substring(1)} ${nodeApiFile}` + ) + // Because you might also update webpack config in saber-node.js + // Which we can't (?) automatically reload + log.warn(`You probably need to restart the server.`) + } require('chokidar') .watch(nodeApiFile, { ignoreInitial: true }) - .on('all', async action => { - await updateNodeApi() - // Remove all child pages - api.pages.removeWhere(page => page.internal.parent) - await Promise.all( - [...api.pages.values()].map(async page => { - // Recreate the page - api.pages.createPage(page) - // A page has been created - await api.hooks.onCreatePage.promise(page) - }) - ) - // All pages are created - await api.hooks.onCreatePages.promise() - // Emit pages - await api.hooks.emitPages.promise() - // Emit route file - await api.hooks.emitRoutes.promise() - log.warn( - `${action[0].toUpperCase()}${action.substring(1)} ${nodeApiFile}` - ) - // Because you might also update webpack config in saber-node.js - // Which we can't (?) automatically reload - log.warn(`You probably need to restart the server.`) + .on('all', action => { + onChange(action) }) } } @@ -94,7 +101,3 @@ exports.apply = api => { handleNodeApiFile(path.join(api.theme, 'saber-node.js'), 'theme-node-api') handleNodeApiFile(api.resolveCwd('saber-node.js'), 'user-node-api') } - -function __noopHandler__(arg) { - return arg -} diff --git a/packages/saber/src/plugins/layouts.js b/packages/saber/src/plugins/layouts.js index c617a7bbe..0853f3b65 100644 --- a/packages/saber/src/plugins/layouts.js +++ b/packages/saber/src/plugins/layouts.js @@ -58,6 +58,25 @@ exports.apply = api => { 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('.', { @@ -68,30 +87,23 @@ exports.apply = api => { }, ignoreInitial: true }) - .on('unlinkDir', async dir => { - if (!dir) { - Object.keys(layouts).forEach(name => { - delete layouts[name] - }) - await writeLayouts(themeLayouts, userLayouts) - } + .on('unlinkDir', dir => { + onRemoveDir(dir) }) // Add/Remove layout components chokidar .watch('*.{vue,js}', { cwd: dir, ignoreInitial: true }) - .on('add', async file => { - setLayout(layouts, path.join(dir, file)) - await writeLayouts(themeLayouts, userLayouts) + .on('add', file => { + onAddLayout(file) }) - .on('unlink', async file => { - setLayout(layouts, path.join(dir, file), true) - await writeLayouts(themeLayouts, userLayouts) + .on('unlink', file => { + onRemoveLayout(file) }) } // No need to watch theme layouts if it's from an npm package - if (!/node_modules/.test(themeLayoutsDir)) { + if (!themeLayoutsDir.includes('node_modules')) { watchLayouts(themeLayoutsDir, themeLayouts) } diff --git a/packages/saber/src/plugins/source-pages.js b/packages/saber/src/plugins/source-pages.js index af1bfeb55..844c2d249 100644 --- a/packages/saber/src/plugins/source-pages.js +++ b/packages/saber/src/plugins/source-pages.js @@ -29,6 +29,7 @@ exports.apply = api => { .map(async file => { file.relative = file.path file.absolute = path.join(pagesDir, file.relative) + // eslint-disable-next-line require-atomic-updates file.content = await fs.readFile(file.absolute, 'utf8') log.verbose(`Found page`, colors.dim(file.absolute)) return file @@ -81,6 +82,7 @@ exports.apply = api => { log.verbose(`Emitting page ${outPath}`) await fs.outputFile(outPath, newContentHash, 'utf8') + // eslint-disable-next-line require-atomic-updates page.internal.saved = true }) ) @@ -126,9 +128,15 @@ exports.apply = api => { await api.hooks.emitRoutes.promise() } - watcher.on('add', handler('add')) - watcher.on('unlink', handler('remove')) - watcher.on('change', handler('change')) + watcher.on('add', () => { + handler('add') + }) + watcher.on('unlink', () => { + handler('remove') + }) + watcher.on('change', () => { + handler('change') + }) } }) } diff --git a/packages/saber/src/plugins/transformer-markdown.js b/packages/saber/src/plugins/transformer-markdown.js index 624301fa1..4499898a2 100644 --- a/packages/saber/src/plugins/transformer-markdown.js +++ b/packages/saber/src/plugins/transformer-markdown.js @@ -1,32 +1,6 @@ const ConfigChain = require('../config-chain') const resolvePackage = require('../utils/resolvePackage') -exports.name = 'builtin:transformer-markdown' - -exports.apply = api => { - api.transformers.add('markdown', { - extensions: ['md'], - transform(page) { - const { frontmatter, body } = require('../utils/parseFrontmatter')( - page.content, - page.internal.absolute - ) - Object.assign(page, frontmatter) - page.content = body - page.content = renderMarkdown(api, page) - }, - getPageComponent(page) { - return ` - - ` - } - }) -} - function renderMarkdown(api, page) { const { configDir } = api const { markdown = {} } = api.config @@ -113,3 +87,29 @@ function renderMarkdown(api, page) { return md.render(page.content, env) } + +exports.name = 'builtin:transformer-markdown' + +exports.apply = api => { + api.transformers.add('markdown', { + extensions: ['md'], + transform(page) { + const { frontmatter, body } = require('../utils/parseFrontmatter')( + page.content, + page.internal.absolute + ) + Object.assign(page, frontmatter) + page.content = body + page.content = renderMarkdown(api, page) + }, + getPageComponent(page) { + return ` + + ` + } + }) +} diff --git a/packages/saber/src/utils/assetsAttribute.ts b/packages/saber/src/utils/assetsAttribute.ts index f12c076ec..0652a6950 100644 --- a/packages/saber/src/utils/assetsAttribute.ts +++ b/packages/saber/src/utils/assetsAttribute.ts @@ -6,7 +6,7 @@ import { slash, isAbsoluteUrl } from 'saber-utils' * When it's an absolute url or starting with `/` * `/path` is used to reference files in static folder */ -const isExternal = (str: string) => isAbsoluteUrl(str) || /^\//.test(str) +const isExternal = (str: string) => isAbsoluteUrl(str) || str.startsWith('/') const MARK = '@@!!SABER_ASSET_MARK_e5968b9a!!@@' @@ -42,7 +42,4 @@ const requireAssets = (str: string) => return `require("${p1}")` }) -export { - prefixAssets, - requireAssets -} +export { prefixAssets, requireAssets } diff --git a/packages/saber/src/utils/getPermalink.js b/packages/saber/src/utils/getPermalink.js index 41ee8ca88..4725df4a3 100644 --- a/packages/saber/src/utils/getPermalink.js +++ b/packages/saber/src/utils/getPermalink.js @@ -1,5 +1,13 @@ // @ts-check +/** + * Left-pad '0' to number that is smaller than 10 + * @param {number} input + */ +function padZero(input) { + return input < 10 ? `0${input}` : input +} + // Default: // about.md => /about.html // about/index.md => /about @@ -54,11 +62,3 @@ module.exports = (localeNames, page, permalinks) => { .replace(/^\/index(\.html)?$/, '/') .replace(/\/index(\.html)?$/, '') } - -/** - * Left-pad '0' to number that is smaller than 10 - * @param {number} input - */ -function padZero(input) { - return input < 10 ? `0${input}` : input -} diff --git a/packages/saber/src/utils/serveDir.js b/packages/saber/src/utils/serveDir.js index f6e9bcde7..60761cf42 100644 --- a/packages/saber/src/utils/serveDir.js +++ b/packages/saber/src/utils/serveDir.js @@ -7,7 +7,7 @@ const serveStatic = require('serve-static') module.exports = function({ dir, host, port } = {}) { const server = polka() server.use(serveStatic(dir)) - server.use(async (req, res, next) => { + server.use((req, res, next) => { if (req.method !== 'GET') return next() createReadStream(path.join(dir, '404.html')).pipe(res) }) diff --git a/packages/saber/src/vue-renderer/index.js b/packages/saber/src/vue-renderer/index.js index e5e8a039e..f8e84a470 100644 --- a/packages/saber/src/vue-renderer/index.js +++ b/packages/saber/src/vue-renderer/index.js @@ -5,10 +5,27 @@ const { SyncWaterfallHook } = require('tapable') const { readJSON } = require('./utils') const renderHTML = require('./render-html') +function runCompiler(compiler) { + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + if (err) return reject(err) + resolve(stats) + }) + }) +} + function resolveVueApp(...args) { return path.join(__dirname, '../../vue-app', ...args) } +function removeTrailingSlash(input) { + if (input === '/') { + return input + } + + return input.replace(/\/$/, '') +} + const ID = 'vue-renderer' export class VueRenderer { @@ -476,6 +493,7 @@ export class VueRenderer { }) server.get('/_saber/visit-page', async (req, res) => { + // eslint-disable-next-line let [, pathname, hash] = /^([^#]+)(#.+)?$/.exec(req.query.route) || [] pathname = removeTrailingSlash(pathname) const fullPath = pathname + (hash || '') @@ -565,20 +583,3 @@ export class VueRenderer { return server.handler } } - -function runCompiler(compiler) { - return new Promise((resolve, reject) => { - compiler.run((err, stats) => { - if (err) return reject(err) - resolve(stats) - }) - }) -} - -function removeTrailingSlash(input) { - if (input === '/') { - return input - } - - return input.replace(/\/$/, '') -}