Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Support plugin, shortcode & helper files written in Typescript #170

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/Elder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HookOptions> = [];
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<HookOptions> = hooksReq.default || hooksReq;

hooksJs = hookSrcFile.map((hook) => ({
...hook,
$$meta: {
type: 'hooks.js',
addedBy: 'hooks.js',
type: 'hooks',
addedBy: 'hooks',
},
}));
} catch (err) {
Expand Down Expand Up @@ -180,21 +181,21 @@ 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);
const shortcodes: ShortcodeDefs = shortcodeReq.default || shortcodeReq;
shortcodesJs = shortcodes.map((shortcode) => ({
...shortcode,
$$meta: {
type: 'shortcodes.js',
addedBy: 'shortcodes.js',
type: 'shortcodes',
addedBy: 'shortcodes',
},
}));
} catch (err) {
Expand Down
12 changes: 6 additions & 6 deletions src/__tests__/Elder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {} } },
Expand Down Expand Up @@ -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: [
{
Expand All @@ -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: [
{
Expand All @@ -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',
},
},
Expand All @@ -160,7 +160,7 @@ describe('#Elder', () => {
description: 'just for testing',
run: () => Promise.resolve({}),
$$meta: {
type: 'hooks.js',
type: 'hooks',
addedBy: 'validations.spec.ts',
},
},
Expand All @@ -170,7 +170,7 @@ describe('#Elder', () => {
description: 'just for testing',
run: () => Promise.resolve(null),
$$meta: {
type: 'hooks.js',
type: 'hooks',
addedBy: 'validations.spec.ts',
},
},
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/__snapshots__/Elder.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Object {
],
"use": "<ul>
<li>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.</li>
<li>Internally used to automatically populate the helpers object with the helpers found in './src/helpers/index.js'.</li>
<li>Internally used to automatically populate the helpers object with the helpers found in './src/helpers'.</li>
<li>Can be used to set information on the data object that is needed throughout the entire lifecycle. (sitewide settings)</li>
</ul>",
},
Expand Down Expand Up @@ -712,7 +712,7 @@ Object {
],
"use": "<ul>
<li>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.</li>
<li>Internally used to automatically populate the helpers object with the helpers found in './src/helpers/index.js'.</li>
<li>Internally used to automatically populate the helpers object with the helpers found in './src/helpers'.</li>
<li>Can be used to set information on the data object that is needed throughout the entire lifecycle. (sitewide settings)</li>
</ul>",
},
Expand Down Expand Up @@ -1251,8 +1251,8 @@ Object {
},
Object {
"$$meta": Object {
"addedBy": "hooks.js",
"type": "hooks.js",
"addedBy": "hooks",
"type": "hooks",
},
"description": "just for testing",
"hook": "bootstrap",
Expand Down
26 changes: 6 additions & 20 deletions src/__tests__/externalHelpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -80,15 +66,15 @@ describe('#externalHelpers', () => {
});
it('works - userHelpers is a function', async () => {
jest.mock(
`src${sep}helpers${sep}index.js`,
`src${sep}helpers`,
() => () =>
Promise.resolve({
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;
Expand Down
17 changes: 6 additions & 11 deletions src/externalHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 3 additions & 4 deletions src/hooks/hookEntityDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <html> 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 <body> tag.',
Expand All @@ -26,7 +25,7 @@ const hookEntityDefinitions = {
bodyAttributesString: 'Body attributes as a string just before it is written.',
headString: 'The complete <head></head> 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.',
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/hookInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const hookInterface: Array<HookInterface> = [
context: 'Routes, plugins, and hooks have been collected and validated.',
use: `<ul>
<li>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.</li>
<li>Internally used to automatically populate the helpers object with the helpers found in './src/helpers/index.js'.</li>
<li>Internally used to automatically populate the helpers object with the helpers found in './src/helpers'.</li>
<li>Can be used to set information on the data object that is needed throughout the entire lifecycle. (sitewide settings)</li>
</ul>`,
location: 'Elder.ts',
Expand Down
4 changes: 2 additions & 2 deletions src/partialHydration/prepareFindSvelteComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 5 additions & 5 deletions src/plugins/__tests__/plugins.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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: [
{
Expand Down
10 changes: 5 additions & 5 deletions src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)) {
Expand All @@ -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)) {
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions src/rollup/getRollupConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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,
Expand Down
Loading