diff --git a/.gitignore b/.gitignore index 3bd0e8b70..9135188c6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ tmp-*.json .idea/ .vscode +*.tmp.* diff --git a/.prettierignore b/.prettierignore index 8741598a4..321f64a39 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ **/node_modules **/tests/output **/package.json +**/*.ejs diff --git a/packages/cli/lib/lib/webpack/empty.html b/packages/cli/lib/lib/webpack/empty.html new file mode 100644 index 000000000..1a4a02127 --- /dev/null +++ b/packages/cli/lib/lib/webpack/empty.html @@ -0,0 +1 @@ + diff --git a/packages/cli/lib/lib/webpack/inline-loader.js b/packages/cli/lib/lib/webpack/inline-loader.js new file mode 100644 index 000000000..53700c564 --- /dev/null +++ b/packages/cli/lib/lib/webpack/inline-loader.js @@ -0,0 +1,8 @@ +import loaderUtils from 'loader-utils'; + +module.exports = function() { + let { code, filename, cacheable } = loaderUtils.getOptions(this); + if (cacheable === false) this.cacheable(false); + this.resourcePath = filename; + return code.replace(/__PREACT__BANG__/g, '!'); +}; diff --git a/packages/cli/lib/lib/webpack/render-html-plugin.js b/packages/cli/lib/lib/webpack/render-html-plugin.js index 7a54bb093..7288172b6 100644 --- a/packages/cli/lib/lib/webpack/render-html-plugin.js +++ b/packages/cli/lib/lib/webpack/render-html-plugin.js @@ -1,24 +1,57 @@ const { resolve } = require('path'); -const { existsSync } = require('fs'); +const { existsSync, readFileSync } = require('fs'); const HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const prerender = require('./prerender'); const createLoadManifest = require('./create-load-manifest'); const { warn } = require('../../util'); const { info } = require('../../util'); -let template = resolve(__dirname, '../../resources/template.html'); +let defaultTemplate = resolve(__dirname, '../../resources/template.html'); + +function read(path) { + return readFileSync(resolve(__dirname, path), 'utf-8'); +} module.exports = async function(config) { const { cwd, dest, isProd, src } = config; const inProjectTemplatePath = resolve(src, 'template.html'); + let template = defaultTemplate; if (existsSync(inProjectTemplatePath)) { template = inProjectTemplatePath; } + + template = config.template || template; + + let content = read(template); + let hasAbtraction = false; + if (/preact\.headEnd|preact\.bodyEnd/.test(content)) { + hasAbtraction = true; + const headEnd = read('../../resources/head-end.ejs'); + const bodyEnd = read('../../resources/body-end.ejs'); + content = content + .replace( + /<%\s+preact\.title\s+%>/, + '<%= htmlWebpackPlugin.options.title %>' + ) + .replace(/<%\s+preact\.headEnd\s+%>/, headEnd) + .replace(/<%\s+preact\.bodyEnd\s+%>/, bodyEnd); + + // webpack treats every ! as a loader call. We'll replace it back to the + // original in our loader. + content = content.replace(/!/g, '__PREACT__BANG__'); + template = `!!ejs-loader!inline-loader?filename=template.js&code=${content}!${resolve( + __dirname, + 'empty.html' + )}`; + // template = resolve(__dirname, 'template.tmp.ejs'); + // writeFileSync(template, content); + } + const htmlWebpackConfig = values => { const { url, title, ...routeData } = values; return Object.assign(values, { filename: resolve(dest, url.substring(1), 'index.html'), - template: `!!ejs-loader!${config.template || template}`, + template: !hasAbtraction ? `!!ejs-loader!${template}` : template, minify: isProd && { collapseWhitespace: true, removeScriptTypeAttributes: true, diff --git a/packages/cli/lib/lib/webpack/webpack-base-config.js b/packages/cli/lib/lib/webpack/webpack-base-config.js index 9f5abb0d9..3b907007f 100644 --- a/packages/cli/lib/lib/webpack/webpack-base-config.js +++ b/packages/cli/lib/lib/webpack/webpack-base-config.js @@ -139,6 +139,7 @@ module.exports = function(env) { modules: [...nodeModules], alias: { 'proxy-loader': require.resolve('./proxy-loader'), + 'inline-loader': require.resolve('./inline-loader'), }, }, diff --git a/packages/cli/lib/resources/body-end.ejs b/packages/cli/lib/resources/body-end.ejs new file mode 100644 index 000000000..e325d0210 --- /dev/null +++ b/packages/cli/lib/resources/body-end.ejs @@ -0,0 +1,18 @@ +<%= htmlWebpackPlugin.options.ssr() %> +<% if (webpack.assets.filter(entry => entry.name.match(/bundle(\.\w{5})?.esm.js$/)).length > 0) { %> + <% /* Fix for safari < 11 nomodule bug. TODO: Do the following only for safari. */ %> + + + <% + /*Fetch and Promise polyfills are not needed for browsers that support type=module + Please re-evaluate below line if adding more polyfills.*/ + %> + + +<% } else { %> + + +<% } %> + diff --git a/packages/cli/lib/resources/head-end.ejs b/packages/cli/lib/resources/head-end.ejs new file mode 100644 index 000000000..b5bb729aa --- /dev/null +++ b/packages/cli/lib/resources/head-end.ejs @@ -0,0 +1,12 @@ + +<% if (htmlWebpackPlugin.options.manifest.theme_color) { %> + +<% } %> +<% const loadManifest = htmlWebpackPlugin.options.createLoadManifest(compilation.assets, webpack.namedChunkGroups);%> +<% const filesRegexp = htmlWebpackPlugin.options.inlineCss ? /\.(chunk\.\w{5}\.css|js)$/ : /\.(css|js)$/;%> +<% for (const file in loadManifest[htmlWebpackPlugin.options.url]) { %> + <% if (htmlWebpackPlugin.options.preload && file && file.match(filesRegexp)) { %> + <% /* crossorigin for main bundle as that is loaded from `