diff --git a/src/Elder.ts b/src/Elder.ts index 7b32808f..7fcbbe5d 100644 --- a/src/Elder.ts +++ b/src/Elder.ts @@ -134,22 +134,23 @@ class Elder { /** * Finalize hooks - * Import User Hooks.js + * Import User Hooks * Validate Hooks * Filter out hooks that are disabled. */ let hooksJs: Array = []; - const hookSrcPath = path.resolve(this.settings.srcDir, './hooks.js'); + const hookSrcPath = path.resolve(this.settings.srcDir, './hooks'); try { const hooksReq = require(hookSrcPath); const hookSrcFile: Array = hooksReq.default || hooksReq; + hooksJs = hookSrcFile.map((hook) => ({ ...hook, $$meta: { - type: 'hooks.js', - addedBy: 'hooks.js', + type: 'hooks', + addedBy: 'hooks', }, })); } catch (err) { @@ -180,12 +181,12 @@ class Elder { /** * Finalize Shortcodes - * Import User Shortcodes.js + * Import User Shortcodes * Validate Shortcodes */ let shortcodesJs: ShortcodeDefs = []; - const shortcodeSrcPath = path.resolve(this.settings.srcDir, './shortcodes.js'); + const shortcodeSrcPath = path.resolve(this.settings.srcDir, './shortcodes'); try { const shortcodeReq = require(shortcodeSrcPath); @@ -193,8 +194,8 @@ class Elder { shortcodesJs = shortcodes.map((shortcode) => ({ ...shortcode, $$meta: { - type: 'shortcodes.js', - addedBy: 'shortcodes.js', + type: 'shortcodes', + addedBy: 'shortcodes', }, })); } catch (err) { diff --git a/src/__tests__/Elder.spec.ts b/src/__tests__/Elder.spec.ts index 72cf7632..2332c2a5 100644 --- a/src/__tests__/Elder.spec.ts +++ b/src/__tests__/Elder.spec.ts @@ -90,7 +90,7 @@ describe('#Elder', () => { { virtual: true }, ); jest.mock( - path.resolve(process.cwd(), `./test/src/plugins/elder-plugin-upload-s3/index.js`), + path.resolve(process.cwd(), `./test/src/plugins/elder-plugin-upload-s3/index`), () => ({ hooks: [], routes: { 'test-a': { hooks: [], template: 'fakepath/Test.svelte', all: [] }, 'test-b': { data: () => {} } }, @@ -127,7 +127,7 @@ describe('#Elder', () => { { virtual: true }, ); jest.mock( - `${process.cwd()}${sep}test${sep}src${sep}hooks.js`, + `${process.cwd()}${sep}test${sep}src${sep}hooks`, () => ({ default: [ { @@ -141,7 +141,7 @@ describe('#Elder', () => { { virtual: true }, ); jest.mock( - `${process.cwd()}${sep}test${sep}src${sep}plugins${sep}elder-plugin-upload-s3${sep}index.js`, + `${process.cwd()}${sep}test${sep}src${sep}plugins${sep}elder-plugin-upload-s3${sep}index`, () => ({ hooks: [ { @@ -150,7 +150,7 @@ describe('#Elder', () => { description: 'just for testing', run: () => Promise.resolve({ plugin: 'elder-plugin-upload-s3' }), $$meta: { - type: 'hooks.js', + type: 'hooks', addedBy: 'validations.spec.ts', }, }, @@ -160,7 +160,7 @@ describe('#Elder', () => { description: 'just for testing', run: () => Promise.resolve({}), $$meta: { - type: 'hooks.js', + type: 'hooks', addedBy: 'validations.spec.ts', }, }, @@ -170,7 +170,7 @@ describe('#Elder', () => { description: 'just for testing', run: () => Promise.resolve(null), $$meta: { - type: 'hooks.js', + type: 'hooks', addedBy: 'validations.spec.ts', }, }, diff --git a/src/__tests__/__snapshots__/Elder.spec.ts.snap b/src/__tests__/__snapshots__/Elder.spec.ts.snap index 181cf388..b5ebcc7d 100644 --- a/src/__tests__/__snapshots__/Elder.spec.ts.snap +++ b/src/__tests__/__snapshots__/Elder.spec.ts.snap @@ -57,7 +57,7 @@ Object { ], "use": "", }, @@ -712,7 +712,7 @@ Object { ], "use": "", }, @@ -1251,8 +1251,8 @@ Object { }, Object { "$$meta": Object { - "addedBy": "hooks.js", - "type": "hooks.js", + "addedBy": "hooks", + "type": "hooks", }, "description": "just for testing", "hook": "bootstrap", diff --git a/src/__tests__/externalHelpers.spec.ts b/src/__tests__/externalHelpers.spec.ts index 0f4831ea..175c7643 100644 --- a/src/__tests__/externalHelpers.spec.ts +++ b/src/__tests__/externalHelpers.spec.ts @@ -11,22 +11,11 @@ const settings = { }; const query = {}; -class StatSyncError extends Error { - code: 'ENOENT'; - - constructor(msg: string) { - super(msg); - this.code = 'ENOENT'; - } -} - describe('#externalHelpers', () => { beforeEach(() => jest.resetModules()); it('throws', async () => { jest.mock('fs', () => ({ - statSync: jest.fn(() => { - throw new StatSyncError('no file'); - }), + existsSync: jest.fn(() => false), })); // eslint-disable-next-line global-require const externalHelpers = require('../externalHelpers').default; @@ -47,9 +36,7 @@ describe('#externalHelpers', () => { }); it('returns undefined if file is not there', async () => { jest.mock('fs', () => ({ - statSync: jest.fn().mockImplementationOnce(() => { - throw new Error(''); - }), + existsSync: jest.fn().mockImplementationOnce(() => false), })); // eslint-disable-next-line global-require const externalHelpers = require('../externalHelpers').default; @@ -58,15 +45,14 @@ describe('#externalHelpers', () => { }); it('works - userHelpers is not a function', async () => { jest.mock( - `src${sep}helpers${sep}index.js`, - + `src${sep}helpers`, () => ({ userHelper: () => 'something', }), { virtual: true }, ); jest.mock('fs', () => ({ - statSync: jest.fn().mockImplementationOnce(() => {}), + existsSync: jest.fn().mockImplementationOnce(() => true), })); // eslint-disable-next-line global-require const externalHelpers = require('../externalHelpers').default; @@ -80,7 +66,7 @@ describe('#externalHelpers', () => { }); it('works - userHelpers is a function', async () => { jest.mock( - `src${sep}helpers${sep}index.js`, + `src${sep}helpers`, () => () => Promise.resolve({ userHelper: () => 'something', @@ -88,7 +74,7 @@ describe('#externalHelpers', () => { { virtual: true }, ); jest.mock('fs', () => ({ - statSync: jest.fn().mockImplementationOnce(() => {}), + existsSync: jest.fn().mockImplementationOnce(() => true), })); // eslint-disable-next-line global-require const externalHelpers = require('../externalHelpers').default; diff --git a/src/externalHelpers.ts b/src/externalHelpers.ts index 57c4c508..12c3d6b6 100644 --- a/src/externalHelpers.ts +++ b/src/externalHelpers.ts @@ -10,24 +10,19 @@ let userHelpers; let cache; async function externalHelpers({ settings, query, helpers }: ExternalHelperRequestOptions) { - const srcHelpers = path.join(settings.srcDir, 'helpers/index.js'); + const srcHelpers = path.join(settings.srcDir, 'helpers'); if (!cache) { - try { - fs.statSync(srcHelpers); + if (fs.existsSync(`${srcHelpers}${path.sep}index.js`) || fs.existsSync(`${srcHelpers}${path.sep}index.ts`)) { userHelpers = require(srcHelpers); if (typeof userHelpers === 'function') { userHelpers = await userHelpers({ settings, query, helpers }); } cache = userHelpers; - } catch (err) { - if (err.code === 'ENOENT') { - if (settings.debug.automagic) { - console.log( - `debug.automagic:: We attempted to automatically add in helpers, but we couldn't find the file at ${srcHelpers}.`, - ); - } - } + } else if (settings.debug.automagic) { + console.log( + `debug.automagic:: We attempted to automatically add in helpers, but we couldn't find the file at ${srcHelpers}.`, + ); } } else { userHelpers = cache; diff --git a/src/hooks/hookEntityDefinitions.ts b/src/hooks/hookEntityDefinitions.ts index 90c51dd8..ac490571 100644 --- a/src/hooks/hookEntityDefinitions.ts +++ b/src/hooks/hookEntityDefinitions.ts @@ -4,14 +4,13 @@ const hookEntityDefinitions = { hookInterface: 'The hook interface is what defines the "contract" for each hook. It includes what properties the hook has access to and which of those properties can be mutated.', errors: 'An array of errors collected during the build process.', - helpers: - 'An object of helpers loaded from `./src/helpers/index.js` in addition to the Elder.js provided helper functions.', + helpers: 'An object of helpers loaded from `./src/helpers/` in addition to the Elder.js provided helper functions.', data: 'An object that is passed to Svelte templates as the "data" prop.', settings: 'An object representing the elder.config.js and other details about the build.', routes: 'An object that represents all of the routes registered with Elder.js.', hooks: 'An array of all of the hooks that have been validated by Elder.js.', query: 'An object that is initially empty but is reserved for plugins and sites to add database or api access to.', - route: 'An object representing the specific route (similar to a route.js file) for a specific request.', + route: 'An object representing the specific route (similar to a route file) for a specific request.', htmlAttributesStack: 'A "stack" of attributes to be merged together that are written to the tag.By default, it containt "{lang: "en"}" or an other lang set in your elder.config.js', bodyAttributesStack: 'A "stack" of attributes to be merged together that are written to the tag.', @@ -26,7 +25,7 @@ const hookEntityDefinitions = { bodyAttributesString: 'Body attributes as a string just before it is written.', headString: 'The complete string just before it is written to the head.', request: - 'An object that represents the parameters required to generate a specific page on a specific route. This object originating from the all() query of a route.js file.', + 'An object that represents the parameters required to generate a specific page on a specific route. This object originating from the all() query of a route file.', beforeHydrateStack: 'A "stack" of generally JS script tags that are required to be loaded before a Svelte component is hydrated. This is only written to the page when a Svelte component needs to be hydrated.', hydrateStack: 'A "stack" Svelte components that will be hydrated.', diff --git a/src/hooks/hookInterface.ts b/src/hooks/hookInterface.ts index 81861944..bca39810 100644 --- a/src/hooks/hookInterface.ts +++ b/src/hooks/hookInterface.ts @@ -22,7 +22,7 @@ export const hookInterface: Array = [ context: 'Routes, plugins, and hooks have been collected and validated.', use: `
  • Often used to populate the empty query object with a database or API connection as query is passed to the all() function which is used to generate request objects.
  • -
  • Internally used to automatically populate the helpers object with the helpers found in './src/helpers/index.js'.
  • +
  • Internally used to automatically populate the helpers object with the helpers found in './src/helpers'.
  • Can be used to set information on the data object that is needed throughout the entire lifecycle. (sitewide settings)
`, location: 'Elder.ts', diff --git a/src/partialHydration/prepareFindSvelteComponent.ts b/src/partialHydration/prepareFindSvelteComponent.ts index b3248fc0..c98feefd 100644 --- a/src/partialHydration/prepareFindSvelteComponent.ts +++ b/src/partialHydration/prepareFindSvelteComponent.ts @@ -15,9 +15,9 @@ export const removeHash = (pathWithHash) => { const prepareFindSvelteComponent = ({ ssrFolder, rootDir, clientComponents: clientFolder, distDir }) => { const rootDirFixed = windowsPathFix(rootDir); - const ssrComponents = glob.sync(`${ssrFolder}/**/*.js`).map(windowsPathFix); + const ssrComponents = glob.sync(`${ssrFolder}/**/*.[jt]s`).map(windowsPathFix); const clientComponents = glob - .sync(`${clientFolder}/**/*.js`) + .sync(`${clientFolder}/**/*.[jt]s`) .map((c) => windowsPathFix(`${path.sep}${path.relative(distDir, c)}`)); const cache = new Map(); diff --git a/src/plugins/__tests__/plugins.spec.ts b/src/plugins/__tests__/plugins.spec.ts index 8e503971..cd47ff6c 100644 --- a/src/plugins/__tests__/plugins.spec.ts +++ b/src/plugins/__tests__/plugins.spec.ts @@ -49,13 +49,13 @@ describe('#plugins', () => { jest.mock('fs-extra', () => ({ existsSync: () => true, })); - jest.mock(path.resolve(`./test/src/plugins/elder-plugin-upload-s3/index.js`), () => '', { + jest.mock(path.resolve(`./test/src/plugins/elder-plugin-upload-s3/index`), () => '', { virtual: true, }); - jest.mock(path.resolve(`./test/node_modules/elder-plugin-upload-s3/package.json`), () => ({ main: './index.js' }), { + jest.mock(path.resolve(`./test/node_modules/elder-plugin-upload-s3/package.json`), () => ({ main: './index.ts' }), { virtual: true, }); - jest.mock(path.resolve(`./test/node_modules/elder-plugin-upload-s3/index.js`), () => '', { + jest.mock(path.resolve(`./test/node_modules/elder-plugin-upload-s3/index.ts`), () => '', { virtual: true, }); // eslint-disable-next-line global-require @@ -90,7 +90,7 @@ describe('#plugins', () => { })); const initMock = jest.fn().mockImplementation((p) => Promise.resolve(p)); jest.mock( - path.resolve(`./test/src/plugins/elder-plugin-upload-s3/index.js`), + path.resolve(`./test/src/plugins/elder-plugin-upload-s3/index`), () => ({ hooks: [ { @@ -148,7 +148,7 @@ describe('#plugins', () => { })); const initMock = jest.fn().mockImplementation((p) => Promise.resolve(p)); jest.mock( - path.resolve(`./test/src/plugins/elder-plugin-upload-s3/index.js`), + path.resolve(`./test/src/plugins/elder-plugin-upload-s3/index`), () => ({ hooks: [ { diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 68614c96..5f2f2d7c 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -28,8 +28,8 @@ async function plugins(elder: Elder) { const pluginConfigFromConfig = elder.settings.plugins[pluginName]; let plugin: PluginOptions | undefined; - const pluginPath = `./plugins/${pluginName}/index.js`; - const srcPlugin = path.resolve(elder.settings.srcDir, pluginPath); + const pluginPath = `./plugins/${pluginName}`; + const srcPlugin = path.resolve(elder.settings.srcDir, pluginPath, 'index'); if (fs.existsSync(srcPlugin)) { // eslint-disable-next-line import/no-dynamic-require @@ -140,7 +140,7 @@ async function plugins(elder: Elder) { const templateName = plugin.routes[routeName].template.replace('.svelte', ''); const ssrComponent = path.resolve( elder.settings.$$internal.ssrComponents, - `./plugins/${pluginName}/${templateName}.js`, + `./plugins/${pluginName}/${templateName}`, ); if (!fs.existsSync(ssrComponent)) { @@ -167,7 +167,7 @@ async function plugins(elder: Elder) { const layoutName = plugin.routes[routeName].layout.replace('.svelte', ''); const ssrComponent = path.resolve( elder.settings.$$internal.ssrComponents, - `./plugins/${pluginName}/${layoutName}.js`, + `./plugins/${pluginName}/${layoutName}`, ); if (!fs.existsSync(ssrComponent)) { @@ -178,7 +178,7 @@ async function plugins(elder: Elder) { plugin.routes[routeName].layoutComponent = svelteComponent(layoutName); } else { plugin.routes[routeName].layout = 'Layout.svelte'; - const ssrComponent = path.resolve(elder.settings.$$internal.ssrComponents, `./layouts/Layout.js`); + const ssrComponent = path.resolve(elder.settings.$$internal.ssrComponents, `./layouts/Layout`); if (!fs.existsSync(ssrComponent)) { console.error( diff --git a/src/rollup/getRollupConfig.ts b/src/rollup/getRollupConfig.ts index 4cc16b49..820bc3fa 100644 --- a/src/rollup/getRollupConfig.ts +++ b/src/rollup/getRollupConfig.ts @@ -19,7 +19,7 @@ const production = process.env.NODE_ENV === 'production' || !process.env.ROLLUP_ const elderJsDir = path.resolve(process.cwd(), './node_modules/@elderjs/elderjs/'); const babelIE11 = babel({ - extensions: ['.js', '.mjs', '.html', '.svelte'], + extensions: ['.js', '.ts', '.mjs', '.html', '.svelte'], runtimeHelpers: true, exclude: ['node_modules/@babel/**', 'node_modules/core-js/**', /\/core-js\//], presets: [ @@ -90,7 +90,7 @@ export function createBrowserConfig({ if (!ie11) { config.plugins.push( babel({ - extensions: ['.js', '.mjs', '.cjs', '.html', '.svelte'], + extensions: ['.js', '.ts', '.mjs', '.cjs', '.html', '.svelte'], include: ['node_modules/**', 'src/**'], exclude: ['node_modules/@babel/**'], runtimeHelpers: true, diff --git a/src/routes/routes.ts b/src/routes/routes.ts index fbbf8771..275293f1 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -10,23 +10,26 @@ import { SettingsOptions } from '../utils/types'; import wrapPermalinkFn from '../utils/wrapPermalinkFn'; function routes(settings: SettingsOptions) { - const files = glob.sync(`${settings.srcDir}/routes/*/+(*.js|*.svelte)`); + const files = glob.sync(`${settings.srcDir}/routes/*/+(*.[jt]s|*.svelte)`); const { ssrComponents: ssrFolder, logPrefix } = settings.$$internal; - const ssrComponents = glob.sync(`${ssrFolder}/**/*.js`); + const ssrComponents = glob.sync(`${ssrFolder}/**/*.[jt]s`); - const routejsFiles = files.filter((f) => f.endsWith('/route.js')); + const routejsFiles = files.filter((f) => /\/route\.[jt]s$/.test(f)); const output = routejsFiles.reduce((out, cv) => { - const routeName = cv.replace('/route.js', '').split('/').pop(); + const routeName = cv + .replace(/\/route\.[jt]s/, '') + .split('/') + .pop(); const capitalizedRoute = capitalizeFirstLetter(routeName); const routeReq = require(cv); const route: RouteOptions = routeReq.default || routeReq; const filesForThisRoute = files .filter((r) => r.includes(`/routes/${routeName}`)) - .filter((r) => !r.includes('route.js')); + .filter((r) => !/route\.[jt]s/.test(r)); if (!route.permalink) { if (settings.debug.automagic) { @@ -58,7 +61,7 @@ function routes(settings: SettingsOptions) { if (route.template) { if (typeof route.template === 'string') { const componentName = route.template.replace('.svelte', ''); - const ssrComponent = ssrComponents.find((f) => f.endsWith(`/routes/${routeName}/${componentName}.js`)); + const ssrComponent = ssrComponents.find((f) => /\/routes\/${routeName}\/${componentName}\.[jt]s$/.test(f)); if (!ssrComponent) { console.error( `We see you want to load ${route.template}, but we don't see a compiled template in ${settings.$$internal.ssrComponents}. You'll probably see more errors in a second. Make sure you've run rollup.`, @@ -80,7 +83,9 @@ function routes(settings: SettingsOptions) { route.template = `${capitalizedRoute}.svelte`; route.templateComponent = svelteComponent(svelteFile, 'routes'); - const ssrComponent = ssrComponents.find((f) => f.endsWith(`/routes/${routeName}/${capitalizedRoute}.js`)); + const ssrComponent = ssrComponents.find((f) => + new RegExp(`\\/routes\\/${routeName}\\/${capitalizedRoute}.[jt]s$`).test(f), + ); if (!ssrComponent) { console.error( `We see you want to load ${route.template}, but we don't see a compiled template in ${settings.$$internal.ssrComponents}. You'll probably see more errors in a second. Make sure you've run rollup.`, @@ -94,13 +99,13 @@ function routes(settings: SettingsOptions) { } if (!route.data) { - const dataFile = filesForThisRoute.find((f) => f.endsWith(`data.js`)); + const dataFile = filesForThisRoute.find((f) => /data\.[jt]s$/.test(f)); if (dataFile) { // TODO: v1 removal const dataReq = require(dataFile); route.data = dataReq.default || dataReq; console.warn( - `WARN: Loading your /routes/${routeName}/data.js file. This functionality is deprecated. Please include your data function in your /routes/${routeName}/route.js object under the 'data' key. As a quick fix you can just import the existing data file and include it as "data" key.`, + `WARN: Loading your /routes/${routeName}/data file. This functionality is deprecated. Please include your data function in your /routes/${routeName}/route object under the 'data' key. As a quick fix you can just import the existing data file and include it as "data" key.`, ); } else { route.data = (page) => { @@ -117,7 +122,7 @@ function routes(settings: SettingsOptions) { } else { if (settings.debug.automagic) { console.log( - `${logPrefix} The route at /routes/${routeName}/route.js doesn't have a layout specified so going to look for a Layout.svelte file.`, + `${logPrefix} The route at /routes/${routeName}/route doesn't have a layout specified so going to look for a Layout.svelte file.`, ); } route.layout = 'Layout.svelte'; diff --git a/src/utils/__tests__/getPluginLocations.spec.ts b/src/utils/__tests__/getPluginLocations.spec.ts index 2cda69c6..6bda0596 100644 --- a/src/utils/__tests__/getPluginLocations.spec.ts +++ b/src/utils/__tests__/getPluginLocations.spec.ts @@ -6,7 +6,6 @@ describe('#getPluginLocations', () => { jest.mock('glob', () => ({ sync: jest .fn() - .mockImplementationOnce(() => [ '/src/plugins/elderjs-plugin-reload/SimplePlugin.svelte', '/src/plugins/elderjs-plugin-reload/Test.svelte', @@ -15,11 +14,32 @@ describe('#getPluginLocations', () => { })); jest.mock('fs-extra', () => ({ - existsSync: jest - .fn() - .mockImplementationOnce(() => true) // first plugin from src - .mockImplementationOnce(() => false) // 2nd from node modules - .mockImplementationOnce(() => true), + existsSync: jest.fn().mockImplementation((filepath: string) => { + if ( + filepath.endsWith('src/plugins/@elderjs/plugin-browser-reload/index.js') || + filepath.endsWith('src/plugins/@elderjs/plugin-browser-reload/index.ts') + ) { + return false; + } + + if (filepath.endsWith('src/plugins/elderjs-plugin-reload/index.js')) { + return false; + } + + if (filepath.endsWith('src/plugins/elderjs-plugin-reload/index.ts')) { + return true; + } + + if (filepath.endsWith('node_modules/@elderjs/plugin-browser-reload/package.json')) { + return true; + } + + if (filepath.endsWith('node_modules/elderjs-plugin-reload/package.json')) { + return false; + } + + return jest.requireActual('fs-extra').existsSync(path); + }), })); expect( diff --git a/src/utils/getPluginLocations.ts b/src/utils/getPluginLocations.ts index 6b21ac86..ea35530d 100644 --- a/src/utils/getPluginLocations.ts +++ b/src/utils/getPluginLocations.ts @@ -4,6 +4,10 @@ import path from 'path'; import fs from 'fs-extra'; import { SettingsOptions } from '..'; +function resolveAndCheckIfExists(filepath: string) { + return fs.existsSync(`${filepath}.js`) || fs.existsSync(`${filepath}.ts`); +} + export default function getPluginLocations(elderConfig: SettingsOptions) { const pluginNames = Object.keys(elderConfig.plugins); @@ -12,7 +16,7 @@ export default function getPluginLocations(elderConfig: SettingsOptions) { const pluginPath = path.resolve(elderConfig.srcDir, `./plugins/${pluginName}`); const nmPluginPath = path.resolve(elderConfig.rootDir, `./node_modules/${pluginName}`); - if (fs.existsSync(`${pluginPath}/index.js`)) { + if (resolveAndCheckIfExists(`${pluginPath}/index`)) { const svelteFiles = glob.sync(`${pluginPath}/*.svelte`); if (svelteFiles.length > 0) { out.paths.push(`${pluginPath}/`);