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

Fix/prerender urls esm #1667

Merged
merged 9 commits into from
Mar 22, 2022
Merged
5 changes: 5 additions & 0 deletions .changeset/dirty-socks-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'preact-cli': patch
---

Allows users to author prerender-urls.js as ESM once again
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,9 @@ preact build --prerenderUrls src/prerender-urls.json
If a static JSON file is too restrictive, you may want to provide a javascript file that exports your routes instead.
Routes can be exported as a JSON string or an object and can optionally be returned from a function.

> `prerender-urls.js`

```js
// prerender-urls.js
module.exports = [
{
url: '/',
Expand All @@ -279,6 +280,22 @@ module.exports = [
];
```

> `prerender-urls.js`

```js
export default () => {
return [
{
url: '/',
title: 'Homepage',
},
{
url: '/route/random',
},
];
};
```

#### Template

A template is used to render your page by [EJS](https://ejs.co/).
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ module.exports = {

// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",

// TODO: Restored in #1667, remove when upgrading Jest
testEnvironment: 'node',
};
32 changes: 13 additions & 19 deletions packages/cli/lib/lib/webpack/render-html-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ const HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plug
const HtmlWebpackPlugin = require('html-webpack-plugin');
const prerender = require('./prerender');
const createLoadManifest = require('./create-load-manifest');
const { warn } = require('../../util');
const { info } = require('../../util');
const { esmImport, tryResolveConfig, warn } = require('../../util');

const PREACT_FALLBACK_URL = '/200.html';

Expand Down Expand Up @@ -107,21 +106,27 @@ module.exports = async function (config) {
let pages = [{ url: '/' }];

if (config.prerenderUrls) {
if (existsSync(resolve(cwd, config.prerenderUrls))) {
const prerenderUrls = tryResolveConfig(
cwd,
config.prerenderUrls,
config.prerenderUrls === 'prerender-urls.json',
config.verbose
);

if (prerenderUrls) {
try {
let result = require(resolve(cwd, config.prerenderUrls));
let result = esmImport(prerenderUrls);

if (typeof result.default !== 'undefined') {
result = result.default();
result = result.default;
}
if (typeof result === 'function') {
info(`Fetching URLs from ${config.prerenderUrls}`);
result = await result();
info(`Fetched URLs from ${config.prerenderUrls}`);
Comment on lines -117 to -119
Copy link
Member Author

@rschristian rschristian Feb 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these info messages really provided any useful information.

}
if (typeof result === 'string') {
result = JSON.parse(result);
}
if (result instanceof Array) {
if (Array.isArray(result)) {
Comment on lines -124 to +129
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this stopped working, but probably shouldn't be using instanceof anyhow

pages = result;
}
} catch (error) {
Expand All @@ -131,17 +136,6 @@ module.exports = async function (config) {
}`
);
}
// don't warn if the default file doesn't exist
} else if (
config.prerenderUrls !== 'prerender-urls.json' ||
config.verbose
) {
warn(
`prerenderUrls file (${resolve(
cwd,
config.prerenderUrls
)}) doesn't exist, using default!`
);
}
}
/**
Expand Down
36 changes: 19 additions & 17 deletions packages/cli/lib/lib/webpack/transform-config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { resolve } = require('path');
const webpack = require('webpack');
const { stat } = require('fs').promises;
const { error } = require('../../util');
const { error, esmImport, tryResolveConfig, warn } = require('../../util');

const FILE = 'preact.config';
const EXTENSIONS = ['js', 'json'];
Expand Down Expand Up @@ -95,24 +95,26 @@ module.exports = async function (env, webpackConfig, isServer = false) {
env.config !== 'preact.config.js'
? { configFile: env.config, isDefault: false }
: await findConfig(env);
env.config = configFile;
let myConfig = resolve(env.cwd, env.config);

try {
await stat(myConfig);
} catch (e) {
if (isDefault) return;
throw new Error(
`preact-cli config could not be loaded!\nFile ${env.config} not found.`
);
}
const cliConfig = tryResolveConfig(
env.cwd,
configFile,
isDefault,
env.verbose
);

let m = require('esm')(module)(myConfig);
if (!cliConfig) return;

// The line above results in an empty object w/ Jest,
// so we need to do the following in order to load it:
if (Object.keys(m).length === 0) {
m = require(myConfig);
let m;
try {
m = esmImport(cliConfig);
} catch (error) {
warn(
`Failed to load preact-cli config file, using default!\n${
env.verbose ? error.stack : error.message
}`
);
return;
}

const transformers = parseConfig((m && m.default) || m);
Expand All @@ -131,7 +133,7 @@ module.exports = async function (env, webpackConfig, isServer = false) {
options
);
} catch (err) {
throw new Error((`Error at ${myConfig}: \n` + err && err.stack) || err);
throw new Error((`Error at ${cliConfig}: \n` + err && err.stack) || err);
}
}
};
Expand Down
15 changes: 13 additions & 2 deletions packages/cli/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ exports.info = function (text, code) {
code && process.exit(code);
};

exports.warn = function (text, code) {
const warn = (exports.warn = function (text, code) {
process.stdout.write(symbols.warning + yellow(' WARN ') + text + '\n');
code && process.exit(code);
};
});

exports.error = function (text, code = 1) {
process.stderr.write(symbols.error + red(' ERROR ') + text + '\n');
Expand All @@ -56,6 +56,8 @@ exports.toBool = function (val) {
return val === void 0 || (val === 'false' ? false : val);
};

exports.esmImport = require('esm')(module);

/**
* Taken from: https://github.com/preactjs/wmr/blob/3401a9bfa6491d25108ad68688c067a7e17d0de5/packages/wmr/src/lib/net-utils.js#L4-Ll4
* Check if a port is free
Expand All @@ -78,3 +80,12 @@ exports.isPortFree = async function (port) {
return false;
}
};

exports.tryResolveConfig = function (cwd, file, isDefault, verbose) {
const path = resolve(cwd, file);
if (existsSync(path)) {
return path;
} else if (!isDefault || verbose) {
warn(`${resolve(cwd, file)} doesn't exist, using default!`);
}
};
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
"homepage": "https://github.com/preactjs/preact-cli",
"devDependencies": {
"@types/express": "^4.17.13",
"@types/jest": "^27.4.0",
"@types/jest": "^24.9.1",
"html-looks-like": "^1.0.2",
"jest": "^27.0.1",
"jest": "^24.9.0",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chased down the weird config file hack I introduced in #1623, turns out to be an issue in esm (the package, not the format) in Jest > v25.

Issue filed at jestjs/jest#12493, but I needed to use ESM within Jest and that hack only worked for CJS config files in our test suite. Downgrading shouldn't be problematic, so I went this route.

"less": "^4.1.1",
"less-loader": "^7.3.0",
"ncp": "^2.0.0",
Expand Down
63 changes: 63 additions & 0 deletions packages/cli/tests/config-formats.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const { join } = require('path');
const { access } = require('fs').promises;
const { build } = require('./lib/cli');
const { subject } = require('./lib/output');

const formats = ['cjs', 'esm'];

const prerenderUrlFiles = [
'array.js',
'stringified-array.js',
'function-returning-array.js',
'function-returning-stringified-array.js',
];

const preactConfigFiles = ['function.js', 'object.js'];

describe('config files', () => {
describe('prerender-urls', () => {
it(`should load the 'prerender-urls.json' file`, async () => {
let dir = await subject('multiple-config-files');

await build(dir);

expect(await access(join(dir, 'build/index.html'))).toBeUndefined();
expect(
await access(join(dir, 'build/custom/index.html'))
).toBeUndefined();
});

formats.forEach(moduleFormat => {
prerenderUrlFiles.forEach(dataFormat => {
it(`should load the '${dataFormat}' file in ${moduleFormat}`, async () => {
let dir = await subject('multiple-config-files');

await build(dir, {
prerenderUrls: `prerenderUrls/${moduleFormat}/${dataFormat}`,
});

expect(await access(join(dir, 'build/index.html'))).toBeUndefined();
expect(
await access(join(dir, 'build/custom/index.html'))
).toBeUndefined();
});
});
});
});

describe('preact.config', () => {
formats.forEach(moduleFormat => {
preactConfigFiles.forEach(dataFormat => {
it(`should load the '${dataFormat}' file in ${moduleFormat}`, async () => {
let dir = await subject('multiple-config-files');

await build(dir, {
config: `preactConfig/${moduleFormat}/${dataFormat}`,
});

expect(await access(join(dir, 'build/bundle.js'))).toBeUndefined();
});
});
});
});
});
19 changes: 19 additions & 0 deletions packages/cli/tests/subjects/multiple-config-files/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { h, Component } from 'preact';
import { Router } from 'preact-router';
import Home from './routes/home';

export default class App extends Component {
handleRoute = e => {
this.currentUrl = e.url;
};

render(props) {
return (
<div id="app">
<Router url={props.url} onChange={this.handleRoute} {...props}>
<Home path="/" />
</Router>
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"private": true,
"name": "preact-config"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function (config) {
config.output.filename = '[name].js';
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
webpack(config) {
config.output.filename = '[name].js';
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function (config) {
config.output.filename = '[name].js';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
webpack(config) {
config.output.filename = '[name].js';
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"url": "/"
},
{
"url": "/custom"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = [
{
url: '/',
},
{
url: '/custom',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = () => [
{
url: '/',
},
{
url: '/custom',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = () => `[
{
"url": "/"
},
{
"url": "/custom"
}
]`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = `[
{
"url": "/"
},
{
"url": "/custom"
}
]`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default [
{
url: '/',
},
{
url: '/custom',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default () => [
{
url: '/',
},
{
url: '/custom',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default () => `[
{
"url": "/"
},
{
"url": "/custom"
}
]`;
Loading