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
5 changes: 5 additions & 0 deletions .changeset/dirty-socks-invite.md
@@ -0,0 +1,5 @@
---
'preact-cli': patch
---

Allows users to author prerender-urls.js as ESM once again
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -279,6 +279,21 @@ module.exports = [
];
```

```js
// prerender-urls.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
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',
};
12 changes: 5 additions & 7 deletions packages/cli/lib/lib/webpack/render-html-plugin.js
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, warn } = require('../../util');

const PREACT_FALLBACK_URL = '/200.html';

Expand Down Expand Up @@ -109,19 +108,18 @@ module.exports = async function (config) {
if (config.prerenderUrls) {
if (existsSync(resolve(cwd, config.prerenderUrls))) {
try {
let result = require(resolve(cwd, config.prerenderUrls));
let result = esmImport(resolve(cwd, config.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

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 Down
29 changes: 16 additions & 13 deletions packages/cli/lib/lib/webpack/transform-config.js
@@ -1,7 +1,7 @@
const { resolve } = require('path');
const webpack = require('webpack');
const { stat } = require('fs').promises;
const { error } = require('../../util');
const { error, esmImport } = require('../../util');

const FILE = 'preact.config';
const EXTENSIONS = ['js', 'json'];
Expand Down Expand Up @@ -98,23 +98,26 @@ module.exports = async function (env, webpackConfig, isServer = false) {
env.config = configFile;
let myConfig = resolve(env.cwd, env.config);

let m;
try {
await stat(myConfig);
} catch (e) {
if (isDefault) return;
m = esmImport(myConfig);
} catch (err) {
const notFound =
err.message.includes('Cannot find module') ||
err.message.includes('Qualified path resolution failed');
if (notFound && isDefault) return;
if (notFound) {
throw new Error(
`Failed to load preact-cli config!\nFile ${env.config} not found.\n`
);
}
throw new Error(
`preact-cli config could not be loaded!\nFile ${env.config} not found.`
`Failed to load preact-cli config!\n${
env.verbose ? err.stack : err.message
}\n`
);
}

let m = require('esm')(module)(myConfig);

// 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);
}

const transformers = parseConfig((m && m.default) || m);

const helpers = new WebpackConfigHelpers(env.cwd);
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/lib/util.js
Expand Up @@ -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 Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
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
65 changes: 65 additions & 0 deletions packages/cli/tests/config.test.js
@@ -0,0 +1,65 @@
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');

const logSpy = jest.spyOn(process.stdout, 'write');

await build(dir);

expect(logSpy).not.toHaveBeenCalledWith(
expect.stringContaining(
'Failed to load prerenderUrls file, using default!'
)
);
});

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

const logSpy = jest.spyOn(process.stdout, 'write');

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

expect(logSpy).not.toHaveBeenCalledWith(
expect.stringContaining(
'Failed to load prerenderUrls file, using default!'
)
);
});
});
});
});

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 expect(
build(dir, { config: `preactConfig/${moduleFormat}/${dataFormat}` })
).resolves.not.toThrow();
});
});
});
});
});
19 changes: 19 additions & 0 deletions packages/cli/tests/subjects/multiple-config-files/index.js
@@ -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>
);
}
}
@@ -0,0 +1,4 @@
{
"private": true,
"name": "preact-config"
}
@@ -0,0 +1 @@
module.exports = function () {};
@@ -0,0 +1,3 @@
module.exports = {
webpack() {},
};
@@ -0,0 +1 @@
export default function () {}
@@ -0,0 +1,3 @@
export default {
webpack() {},
};
@@ -0,0 +1,8 @@
[
{
"url": "/"
},
{
"url": "/custom"
}
]
@@ -0,0 +1,8 @@
module.exports = [
{
url: '/',
},
{
url: '/custom',
},
];
@@ -0,0 +1,8 @@
module.exports = () => [
{
url: '/',
},
{
url: '/custom',
},
];
@@ -0,0 +1,8 @@
module.exports = () => `[
{
"url": "/"
},
{
"url": "/custom"
}
]`;
@@ -0,0 +1,8 @@
module.exports = `[
{
"url": "/"
},
{
"url": "/custom"
}
]`;
@@ -0,0 +1,8 @@
export default [
{
url: '/',
},
{
url: '/custom',
},
];
@@ -0,0 +1,8 @@
export default () => [
{
url: '/',
},
{
url: '/custom',
},
];
@@ -0,0 +1,8 @@
export default () => `[
{
"url": "/"
},
{
"url": "/custom"
}
]`;
@@ -0,0 +1,8 @@
export default `[
{
"url": "/"
},
{
"url": "/custom"
}
]`;
@@ -0,0 +1,3 @@
.home {
background: red;
}
@@ -0,0 +1,3 @@
import './home.css';

export default () => <div>Home</div>;