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

Enhancement/issue 354 restore optimization config for no bundle #477

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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