Skip to content

Commit

Permalink
Enhancement/issue 354 restore optimization config for no bundle (#477)
Browse files Browse the repository at this point in the history
* client side router optimization setting for build

* filter only for anchor tags

* preserve URL on route change

* mode vs optimization configuration

* test cases for MPA

* clean up merge conflict marker

* clarify test it statements

* fix bug with graphql head tag closing

* default optimization for preloading CSS

* only preload CSS

* add none optimization option

* all anticipated optimization settings

* simplify minified output check

* added static optimization setting for JS templating

* introduce inline optimization setting

* inline optimization support enhancements

* fix styling for tables

* documentation for mode and optimization

* use modulepreload for ESM script tags
  • Loading branch information
thescientist13 committed Apr 3, 2021
1 parent 1d23bbe commit 16e19dd
Show file tree
Hide file tree
Showing 51 changed files with 1,153 additions and 189 deletions.
1 change: 1 addition & 0 deletions greenwood.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const FAVICON_HREF = '/assets/favicon.ico';

module.exports = {
workspace: path.join(__dirname, 'www'),
mode: 'mpa',
title: 'Greenwood',
meta: [
{ name: 'description', content: META_DESCRIPTION },
Expand Down
154 changes: 119 additions & 35 deletions packages/cli/src/config/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-depth, no-loop-func */
const Buffer = require('buffer').Buffer;
const fs = require('fs');
const htmlparser = require('node-html-parser');
Expand All @@ -9,7 +10,6 @@ const postcss = require('postcss');
const postcssImport = require('postcss-import');
const replace = require('@rollup/plugin-replace');
const { terser } = require('rollup-plugin-terser');

const tokenSuffix = 'scratch';
const tokenNodeModules = 'node_modules/';

Expand Down Expand Up @@ -98,7 +98,8 @@ function greenwoodWorkspaceResolver (compilation) {
// https://github.com/rollup/rollup/issues/2873
function greenwoodHtmlPlugin(compilation) {
const { projectDirectory, userWorkspace, outputDir, scratchDir } = compilation.context;
const isRemoteUrl = (url = '') => url.indexOf('http') === 0 || url.indexOf('//') === 0;
const { optimization } = compilation.config;
const isRemoteUrl = (url = undefined) => url && (url.indexOf('http') === 0 || url.indexOf('//') === 0);
const customResources = compilation.config.plugins.filter((plugin) => {
return plugin.type === 'resource';
}).map((plugin) => {
Expand Down Expand Up @@ -159,25 +160,29 @@ function greenwoodHtmlPlugin(compilation) {

// handle <script type="module" src="some/path.js"></script>
if (!isRemoteUrl(parsedAttributes.src) && parsedAttributes.type === 'module' && parsedAttributes.src && !mappedScripts.get(parsedAttributes.src)) {
const { src } = parsedAttributes;

// TODO avoid using href and set it to the value of rollup fileName instead
// since user paths can still be the same file,
// e.g. ../theme.css and ./theme.css are still the same file
mappedScripts.set(src, true);
if (optimization === 'static') {
// console.debug('dont emit ', parsedAttributes.src);
} else {
const { src } = parsedAttributes;

const srcPath = src.replace('../', './');
const basePath = srcPath.indexOf(tokenNodeModules) >= 0
? projectDirectory
: userWorkspace;
const source = fs.readFileSync(path.join(basePath, srcPath), 'utf-8');

this.emitFile({
type: 'chunk',
id: srcPath.replace('/node_modules', path.join(projectDirectory, tokenNodeModules)),
name: srcPath.split('/')[srcPath.split('/').length - 1].replace('.js', ''),
source
});
// TODO avoid using href and set it to the value of rollup fileName instead
// since user paths can still be the same file,
// e.g. ../theme.css and ./theme.css are still the same file
mappedScripts.set(src, true);

const srcPath = src.replace('../', './');
const basePath = srcPath.indexOf(tokenNodeModules) >= 0
? projectDirectory
: userWorkspace;
const source = fs.readFileSync(path.join(basePath, srcPath), 'utf-8');

this.emitFile({
type: 'chunk',
id: srcPath.replace('/node_modules', path.join(projectDirectory, tokenNodeModules)),
name: srcPath.split('/')[srcPath.split('/').length - 1].replace('.js', ''),
source
});
}
}

// handle <script type="module">/* some inline JavaScript code */</script>
Expand Down Expand Up @@ -312,10 +317,31 @@ function greenwoodHtmlPlugin(compilation) {
for (const innerBundleId of Object.keys(bundles)) {
const { src } = parsedAttributes;
const facadeModuleId = bundles[innerBundleId].facadeModuleId;
const pathToMatch = src.replace('../', '').replace('./', '');
let pathToMatch = src.replace('../', '').replace('./', '');

// special handling for node_modules paths
if (pathToMatch.indexOf(tokenNodeModules) >= 0) {
pathToMatch = pathToMatch.replace(`/${tokenNodeModules}`, '');

const pathToMatchPieces = pathToMatch.split('/');

pathToMatch = pathToMatch.replace(tokenNodeModules, '');
pathToMatch = pathToMatch.replace(`${pathToMatchPieces[0]}/`, '');
}

if (facadeModuleId && facadeModuleId.indexOf(pathToMatch) > 0) {
newHtml = newHtml.replace(src, `/${innerBundleId}`);
const newSrc = `/${innerBundleId}`;

newHtml = newHtml.replace(src, newSrc);

if (optimization !== 'none' && optimization !== 'inline') {
newHtml = newHtml.replace('<head>', `
<head>
<link rel="modulepreload" href="${newSrc}" as="script">
`);
}
} else if (optimization === 'static' && newHtml.indexOf(pathToMatch) > 0) {
newHtml = newHtml.replace(scriptTag, '');
}
}
}
Expand All @@ -331,7 +357,16 @@ function greenwoodHtmlPlugin(compilation) {
if (bundleId2.indexOf('.css') > 0) {
const bundle2 = bundles[bundleId2];
if (href.indexOf(bundle2.name) >= 0) {
newHtml = newHtml.replace(href, `/${bundle2.fileName}`);
const newHref = `/${bundle2.fileName}`;

newHtml = newHtml.replace(href, newHref);

if (optimization !== 'none' && optimization !== 'inline') {
newHtml = newHtml.replace('<head>', `
<head>
<link rel="preload" href="${newHref}" as="style" crossorigin="anonymous"></link>
`);
}
}
}
}
Expand Down Expand Up @@ -363,10 +398,29 @@ function greenwoodHtmlPlugin(compilation) {
style: true
});
const headScripts = root.querySelectorAll('script');
const headLinks = root.querySelectorAll('link');

headScripts.forEach((scriptTag) => {
const parsedAttributes = parseTagForAttributes(scriptTag);

const isScriptSrcTag = parsedAttributes.src && parsedAttributes.type === 'module';

if (optimization === 'inline' && isScriptSrcTag && !isRemoteUrl(parsedAttributes.src)) {
const src = parsedAttributes.src;
const basePath = src.indexOf(tokenNodeModules) >= 0
? process.cwd()
: outputDir;
const outputPath = path.join(basePath, src);
const js = fs.readFileSync(outputPath, 'utf-8');

// scratchFiles[src] = true;

html = html.replace(`<script ${scriptTag.rawAttrs}></script>`, `
<script type="module">
${js}
</script>
`);
}

// handle <script type="module"> /* inline code */ </script>
if (parsedAttributes.type === 'module' && !parsedAttributes.src) {
for (const innerBundleId of Object.keys(bundles)) {
Expand All @@ -381,6 +435,29 @@ function greenwoodHtmlPlugin(compilation) {
}
});

if (optimization === 'inline') {
headLinks
.forEach((linkTag) => {
const linkTagAttributes = parseTagForAttributes(linkTag);
const isLocalLinkTag = linkTagAttributes.rel === 'stylesheet'
&& !isRemoteUrl(linkTagAttributes.href);

if (isLocalLinkTag) {
const href = linkTagAttributes.href;
const outputPath = path.join(outputDir, href);
const css = fs.readFileSync(outputPath, 'utf-8');

// scratchFiles[href] = true;

html = html.replace(`<link ${linkTag.rawAttrs}>`, `
<style>
${css}
</style>
`);
}
});
}

await fs.promises.writeFile(htmlPath, html);
} else {
const sourcePath = `${outputDir}/${bundleId}`;
Expand All @@ -400,13 +477,29 @@ function greenwoodHtmlPlugin(compilation) {

module.exports = getRollupConfig = async (compilation) => {
const { scratchDir, outputDir } = compilation.context;

const defaultRollupPlugins = [
// TODO replace should come in via plugin-node-modules
replace({ // https://github.com/rollup/rollup/issues/487#issuecomment-177596512
'process.env.NODE_ENV': JSON.stringify('production')
}),
nodeResolve(), // TODO move to plugin-node-modules
greenwoodWorkspaceResolver(compilation),
greenwoodHtmlPlugin(compilation),
multiInput(),
json() // TODO make it part plugin-standard-json
];
// TODO greenwood standard plugins, then "Greenwood" plugins, then user plugins
const customRollupPlugins = compilation.config.plugins.filter((plugin) => {
return plugin.type === 'rollup';
}).map((plugin) => {
return plugin.provider(compilation);
}).flat();

if (compilation.config.optimization !== 'none') {
defaultRollupPlugins.push(
terser() // TODO extract to plugin-standard-javascript
);
}

return [{
// TODO Avoid .greenwood/ directory, do everything in public/?
Expand All @@ -424,16 +517,7 @@ module.exports = getRollupConfig = async (compilation) => {
}
},
plugins: [
// TODO replace should come in via plugins?
replace({ // https://github.com/rollup/rollup/issues/487#issuecomment-177596512
'process.env.NODE_ENV': JSON.stringify('production')
}),
nodeResolve(), // TODO move to plugin
greenwoodWorkspaceResolver(compilation),
greenwoodHtmlPlugin(compilation),
multiInput(),
json(), // TODO bundle as part of import support / transforms API?
terser(), // TODO extract to a plugin
...defaultRollupPlugins,
...customRollupPlugins
]
}];
Expand Down
43 changes: 43 additions & 0 deletions packages/cli/src/lib/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable no-underscore-dangle */
document.addEventListener('click', function(e) {
e.preventDefault();

if (e.path[0].href) {
console.debug('linked clicked was...', e.path[0].href);
const target = e.path[0];
const route = target.href.replace(window.location.origin, '');
const routerOutlet = Array.from(document.getElementsByTagName('greenwood-route')).filter(outlet => {
return outlet.getAttribute('data-route') === route;
})[0];

console.debug('routerOutlet', routerOutlet);

if (routerOutlet.getAttribute('data-template') === window.__greenwood.currentTemplate) {
window.__greenwood.currentTemplate = routerOutlet.getAttribute('data-template');
routerOutlet.loadRoute();
} else {
console.debug('new template detected, should do a hard reload');
window.location.href = target;
}
}
});

class RouteComponent extends HTMLElement {
loadRoute() {
const route = this.getAttribute('data-route');
const key = this.getAttribute('data-key');
console.debug('load route ->', route);
console.debug('with bundle ->', key);
fetch(key)
.then(res => res.text())
.then((response) => {
history.pushState(response, route, route);
document.getElementsByTagName('router-outlet')[0].innerHTML = response;
});
}
}

class RouterOutletComponent extends HTMLElement { }

customElements.define('greenwood-route', RouteComponent);
customElements.define('router-outlet', RouterOutletComponent);
28 changes: 18 additions & 10 deletions packages/cli/src/lifecycles/config.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
const fs = require('fs');
const path = require('path');

// TODO const optimizations = ['strict', 'spa'];
let defaultConfig = {
const modes = ['ssg', 'mpa'];
const optimizations = ['default', 'none', 'static', 'inline'];

const defaultConfig = {
workspace: path.join(process.cwd(), 'src'),
devServer: {
port: 1984
},
optimization: '',
mode: modes[0],
optimization: optimizations[0],
title: 'My App',
meta: [],
plugins: [],
Expand All @@ -23,7 +26,7 @@ module.exports = readAndMergeConfig = async() => {

if (fs.existsSync(path.join(process.cwd(), 'greenwood.config.js'))) {
const userCfgFile = require(path.join(process.cwd(), 'greenwood.config.js'));
const { workspace, devServer, title, markdown, meta, plugins } = userCfgFile;
const { workspace, devServer, title, markdown, meta, mode, optimization, plugins } = userCfgFile;

// workspace validation
if (workspace) {
Expand Down Expand Up @@ -61,12 +64,17 @@ module.exports = readAndMergeConfig = async() => {
customConfig.meta = meta;
}

// TODO
// if (typeof optimization === 'string' && optimizations.indexOf(optimization.toLowerCase()) >= 0) {
// customConfig.optimization = optimization;
// } else if (optimization) {
// reject(`Error: provided optimization "${optimization}" is not supported. Please use one of: ${optimizations.join(', ')}.`);
// }
if (typeof mode === 'string' && modes.indexOf(mode.toLowerCase()) >= 0) {
customConfig.mode = mode;
} else if (mode) {
reject(`Error: provided mode "${mode}" is not supported. Please use one of: ${modes.join(', ')}.`);
}

if (typeof optimization === 'string' && optimizations.indexOf(optimization.toLowerCase()) >= 0) {
customConfig.optimization = optimization;
} else if (optimization) {
reject(`Error: provided optimization "${optimization}" is not supported. Please use one of: ${optimizations.join(', ')}.`);
}

if (plugins && plugins.length > 0) {
const types = ['resource', 'rollup', 'server'];
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/lifecycles/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ const BrowserRunner = require('../lib/browser');
const fs = require('fs');
const path = require('path');
const pluginResourceStandardHtml = require('../plugins/resource/plugin-standard-html');
const pluginOptimizationMpa = require('../plugins/resource/plugin-optimization-mpa');

module.exports = serializeCompilation = async (compilation) => {
const compilationCopy = Object.assign({}, compilation);
const browserRunner = new BrowserRunner();
const optimizeResources = [
pluginResourceStandardHtml.provider(compilationCopy),
pluginOptimizationMpa().provider(compilationCopy),
...compilation.config.plugins.filter((plugin) => {
const provider = plugin.provider(compilationCopy);

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/lifecycles/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require('path');
const Koa = require('koa');

const pluginNodeModules = require('../plugins/resource/plugin-node-modules');
const pluginResourceOptimizationMpa = require('../plugins/resource/plugin-optimization-mpa');
const pluginResourceStandardCss = require('../plugins/resource/plugin-standard-css');
const pluginResourceStandardFont = require('../plugins/resource/plugin-standard-font');
const pluginResourceStandardHtml = require('../plugins/resource/plugin-standard-html');
Expand All @@ -26,6 +27,7 @@ function getDevServer(compilation) {
pluginResourceStandardImage.provider(compilationCopy),
pluginResourceStandardJavaScript.provider(compilationCopy),
pluginResourceStandardJson.provider(compilationCopy),
pluginResourceOptimizationMpa().provider(compilationCopy),

// custom user resource plugins
...compilation.config.plugins.filter((plugin) => {
Expand Down
Loading

0 comments on commit 16e19dd

Please sign in to comment.