Skip to content

Commit

Permalink
refactor: Move EJS into user templates (template.html) (#1768)
Browse files Browse the repository at this point in the history
* refactor: Merging EJS templates and switcing preferred template extension to .ejs

* test: Fixing test suite to reflect latest changes

* chore: Cleaning

* docs: Updating ReadMe

* docs: Adding changeset

* refactor: Add error message for <% preact.(head|body)End %>
  • Loading branch information
rschristian committed Jan 9, 2023
1 parent 8b6ba36 commit 576321c
Show file tree
Hide file tree
Showing 20 changed files with 171 additions and 172 deletions.
9 changes: 9 additions & 0 deletions .changeset/hungry-peas-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'preact-cli': major
---

To increase transparency and user control over the `template.html`, `<% preact.headEnd %>` and `<% preact.bodyEnd %>` will no longer be supported; instead, users should directly adopt the EJS and keep it in their templates.

In the past, these were abstracted away as they were a bit unwieldy; EJS might be unfamiliar with users and the way data was retrieved from `html-webpack-plugin` was somewhat less than elegant. However, this has much improved over the years and the abstraction only makes simple edits less than obvious, so it is no longer fulfilling it's purpose.

New projects will have a `template.ejs` created in place of the old `template.html`, containing the full EJS template. For existing projects, you can copy [the default `template.ejs`](https://github.com/preactjs/preact-cli/blob/master/packages/cli/src/resources/template.ejs) into your project or adapt it as you wish.
18 changes: 9 additions & 9 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
{
"extends": [
"eslint:recommended",
"prettier"
],
"extends": ["eslint:recommended", "prettier"],
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true,
"es6": true
},
"plugins": [
"babel",
"react",
"prettier"
],
"plugins": ["babel", "react", "prettier"],
"settings": {
"react": {
"pragma": "h",
Expand All @@ -29,6 +22,13 @@
"rules": {
"no-console": 1,
"no-empty": 0,
"no-unused-vars": [
2,
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"semi": 2,
"keyword-spacing": 2,
"require-atomic-updates": 0,
Expand Down
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ $ [npm run / yarn] preact build
--babelConfig Path to custom Babel config (default .babelrc)
--prerender Renders route(s) into generated static HTML (default true)
--prerenderUrls Path to pre-rendered routes config (default prerender-urls.json)
--template Path to custom HTML template (default 'src/template.html')
--template Path to custom EJS or HTML template (default 'src/template.ejs')
--inlineCss Adds critical css to the prerendered markup (default true)
--analyze Launch interactive Analyzer to inspect production bundle(s) (default false)
-c, --config Path to custom CLI config (default preact.config.js)
Expand All @@ -151,7 +151,7 @@ $ [npm run / yarn] preact watch
--cacert Path to optional CA certificate override
--prerender Pre-render static content on first run
--prerenderUrls Path to pre-rendered routes config (default prerender-urls.json)
--template Path to custom HTML template (default 'src/template.html')
--template Path to custom EJS or HTML template (default 'src/template.ejs')
--refresh Enables experimental preact-refresh functionality
-c, --config Path to custom CLI config (default preact.config.js)
-H, --host Set server hostname (default 0.0.0.0)
Expand Down Expand Up @@ -211,7 +211,7 @@ To customize Babel, you have two options:

#### Webpack

To customize preact-cli create a `preact.config.js` or a `preact.config.json` file.
To customize Preact-CLI's Webpack config, create a `preact.config.js` or a `preact.config.json` file:

> `preact.config.js`
Expand Down Expand Up @@ -295,18 +295,15 @@ export default () => {

#### Template

A template is used to render your page by [EJS](https://ejs.co/).
You can uses the data of `prerenderUrls` which does not have `title`, using `htmlWebpackPlugin.options.CLI_DATA.preRenderData` in EJS.
To customize the HTML document that your app uses, edit the `template.ejs` file in your app's source directory.

The default one is visible [here](packages/cli/src/resources/template.html) and it's going to be enough for the majority of cases.
[EJS](https://ejs.dev) is a simple templating language that lets you generate HTML markup with plain JavaScript. Alongside `html-webpack-plugin`, you're able to conditionally add HTML, access your bundles and assets, and link to external content if you wish. The default we provide on project initialization should fit the majority of use cases very well, but feel free to customize!

If you want to customise your template you can pass a custom template with the `--template` flag.

The `--template` flag is available on the `build` and `watch` commands.
You can customize the location of your template with the `--template` flag on the `build` and `watch` commands:

```sh
preact build --template src/template.html
preact watch --template src/template.html
preact build --template renamed-src/template.ejs
preact watch --template template.ejs
```

### Using CSS preprocessors
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ prog
.option('--babelConfig', 'Path to custom Babel config', '.babelrc')
.option(
'--template',
'Path to custom HTML template (default src/template.html)'
'Path to custom EJS or HTML template (default src/template.ejs)'
)
.option(
'--analyze',
Expand Down
113 changes: 75 additions & 38 deletions packages/cli/src/lib/webpack/render-html-plugin.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,78 @@
const { mkdtemp, readFile, writeFile } = require('fs/promises');
const { existsSync } = require('fs');
const { resolve, join } = require('path');
const os = require('os');
const { existsSync, mkdtempSync, readFileSync, writeFileSync } = require('fs');
const { Compilation, sources } = require('webpack');
const {
HtmlWebpackSkipAssetsPlugin,
} = require('html-webpack-skip-assets-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const prerender = require('./prerender');
const { esmImport, tryResolveConfig, warn } = require('../../util');
const { esmImport, error, tryResolveConfig, warn } = require('../../util');

const PREACT_FALLBACK_URL = '/200.html';

let defaultTemplate = resolve(__dirname, '../../resources/template.html');

function read(path) {
return readFileSync(resolve(__dirname, path), 'utf-8');
}

/**
* @param {import('../../../types').Env} env
*/
module.exports = async function renderHTMLPlugin(config, env) {
const { cwd, dest, src } = config;

const inProjectTemplatePath = resolve(src, 'template.html');
let template = defaultTemplate;
if (existsSync(inProjectTemplatePath)) {
template = inProjectTemplatePath;
let templatePath;
if (config.template) {
templatePath = tryResolveConfig(
cwd,
config.template,
false,
config.verbose
);
}

if (config.template) {
const templatePathFromArg = resolve(cwd, config.template);
if (existsSync(templatePathFromArg)) template = templatePathFromArg;
else {
warn(`Template not found at ${templatePathFromArg}`);
if (!templatePath) {
templatePath = tryResolveConfig(
cwd,
resolve(src, 'template.ejs'),
true,
config.verbose
);

// Additionally checks for <src-dir>/template.html for
// back-compat with v3
if (!templatePath) {
templatePath = tryResolveConfig(
cwd,
resolve(src, 'template.html'),
true,
config.verbose
);
}

if (!templatePath)
templatePath = resolve(__dirname, '../../resources/template.ejs');
}

let content = read(template);
if (/preact\.(title|headEnd|bodyEnd)/.test(content)) {
const headEnd = read('../../resources/head-end.ejs');
const bodyEnd = read('../../resources/body-end.ejs');
content = content
.replace(/<%[=]?\s+preact\.title\s+%>/, '<%= cli.title %>')
.replace(/<%\s+preact\.headEnd\s+%>/, headEnd)
.replace(/<%\s+preact\.bodyEnd\s+%>/, bodyEnd);
let templateContent = await readFile(templatePath, 'utf-8');
if (/preact\.(?:headEnd|bodyEnd)/.test(templateContent)) {
const message = `
'<% preact.headEnd %>' and '<% preact.bodyEnd %>' are no longer supported in CLI v4!
You can copy the new default 'template.ejs' from the following link or adapt your existing:
https://github.com/preactjs/preact-cli/blob/master/packages/cli/src/resources/template.ejs
`;

error(message.trim().replace(/^\t+/gm, '') + '\n');
}
if (/preact\.title/.test(templateContent)) {
templateContent = templateContent.replace(
/<%[=]?\s+preact\.title\s+%>/,
'<%= cli.title %>'
);

// Unfortunately html-webpack-plugin expects a true file,
// so we'll create a temporary one.
const tmpDir = mkdtempSync(join(os.tmpdir(), 'preact-cli-'));
template = resolve(tmpDir, 'template.tmp.ejs');
writeFileSync(template, content);
const tmpDir = await mkdtemp(join(os.tmpdir(), 'preact-cli-'));
templatePath = resolve(tmpDir, 'template.tmp.ejs');
await writeFile(templatePath, templateContent);
}

const htmlWebpackConfig = values => {
Expand All @@ -72,29 +94,46 @@ module.exports = async function renderHTMLPlugin(config, env) {
return {
title,
filename,
template: `!!${require.resolve('ejs-loader')}?esModule=false!${template}`,
template: `!!${require.resolve(
'ejs-loader'
)}?esModule=false!${templatePath}`,
templateParameters: async (compilation, assets, assetTags, options) => {
let entrypoints = {};
compilation.entrypoints.forEach((entrypoint, name) => {
let entryFiles = entrypoint.getFiles();

entrypoints[name] =
assets.publicPath +
entryFiles.find(file => /\.(m?js)(\?|$)/.test(file));
entryFiles.find(file => /\.m?js(?:\?|$)/.test(file));
});

const compilationAssets = Object.keys(compilation.assets);
const outputName = entrypoints['bundle']
.match(/^([^.]*)/)[1]
.replace(assets.publicPath, '');
const rgx = new RegExp(`${outputName}.*legacy.js$`);
const legacyOutput = compilationAssets.find(file => rgx.test(file));
if (legacyOutput) {
entrypoints['legacy-bundle'] = assets.publicPath + legacyOutput;
}

if (compilationAssets.includes('es-polyfills.js')) {
entrypoints['es-polyfills'] = assets.publicPath + 'es-polyfills.js';
}

return {
cli: {
title,
url,
manifest: config.manifest,
inlineCss: config['inlineCss'],
config,
// pkg isn't likely to be useful and manifest is already made available
config: (({ pkg: _pkg, manifest: _manifest, ...rest }) => rest)(
config
),
env,
preRenderData: values,
CLI_DATA: { preRenderData: { url, ...routeData } },
ssr: config.prerender
? await prerender(config, values)
: '',
ssr: config.prerender ? await prerender(config, values) : '',
entrypoints,
},
htmlWebpackPlugin: {
Expand Down Expand Up @@ -195,7 +234,7 @@ class PrerenderDataExtractPlugin {
}
let path = this.location_ + 'preact_prerender_data.json';
if (path.startsWith('/')) {
path = path.substr(1);
path = path.substring(1);
}
compilation.emitAsset(path, new sources.RawSource(this.data_));
}
Expand All @@ -204,5 +243,3 @@ class PrerenderDataExtractPlugin {
);
}
}

exports.PREACT_FALLBACK_URL = PREACT_FALLBACK_URL;
14 changes: 0 additions & 14 deletions packages/cli/src/resources/body-end.ejs

This file was deleted.

4 changes: 0 additions & 4 deletions packages/cli/src/resources/head-end.ejs

This file was deleted.

41 changes: 0 additions & 41 deletions packages/cli/src/resources/static-app.json

This file was deleted.

27 changes: 27 additions & 0 deletions packages/cli/src/resources/template.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><% preact.title %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" href="<%= htmlWebpackPlugin.files.publicPath %>assets/icons/apple-touch-icon.png">
<link rel="manifest" href="<%= htmlWebpackPlugin.files.publicPath %>manifest.json">
<% if (cli.manifest.theme_color) { %>
<meta name="theme-color" content="<%= cli.manifest.theme_color %>">
<% } %>
</head>
<body>
<%= cli.ssr %>
<% if (cli.config.prerender === true) { %>
<script type="__PREACT_CLI_DATA__">
<%= encodeURI(JSON.stringify(cli.CLI_DATA)) %>
</script>
<% } %>
<script type="module" src="<%= cli.entrypoints['bundle'] %>"></script>
<script nomodule src="<%= cli.entrypoints['dom-polyfills'] %>"></script>
<script nomodule src="<%= cli.entrypoints['es-polyfills'] %>"></script>
<script nomodule defer src="<%= cli.entrypoints['legacy-bundle'] %>"></script>
</body>
</html>
15 changes: 0 additions & 15 deletions packages/cli/src/resources/template.html

This file was deleted.

Loading

0 comments on commit 576321c

Please sign in to comment.