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

Rfc/issue 185 resources refactor #466

Merged
merged 24 commits into from
Jan 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2016aa4
initial transforms refactor using Resources approach
thescientist13 Jan 10, 2021
55e6294
POC of a FooResource
thescientist13 Jan 13, 2021
c0b51dc
make resource resolve async and resource support for css js and json
thescientist13 Jan 13, 2021
7a138af
support standard font and image resources
thescientist13 Jan 13, 2021
428e950
integrate markdown resource handling
thescientist13 Jan 13, 2021
308b85c
WIP resource lifecycle refactor
thescientist13 Jan 16, 2021
f9723eb
refactor all plugins
thescientist13 Jan 16, 2021
e59da4a
comments and code cleanup
thescientist13 Jan 16, 2021
8ce6b7c
delete old transforms
thescientist13 Jan 16, 2021
c37dd9b
filename change for consistency
thescientist13 Jan 16, 2021
d09bf37
custom file extension integration with rollup
thescientist13 Jan 16, 2021
9a941d7
fix existing test cases to restore expected outcomes post refactor
thescientist13 Jan 18, 2021
c4228c0
fix .ico resolution by using url
thescientist13 Jan 18, 2021
9488822
made spec for a foo resource plugin
thescientist13 Jan 18, 2021
c4d7fd6
delete old plugin specs, rename existing specs, added plugin name spec
thescientist13 Jan 18, 2021
a8831aa
refactor and restore google analytics plugin
thescientist13 Jan 18, 2021
2fdef53
refactor and restore polyfills plugin
thescientist13 Jan 18, 2021
d0330d7
plugin documentation refactoring
thescientist13 Jan 18, 2021
c85396d
add examples and rename
thescientist13 Jan 19, 2021
9d6c6f8
refactor serialize teardown into standard html optimize
thescientist13 Jan 20, 2021
41e016f
remove unfinished sentance from docs
thescientist13 Jan 20, 2021
6cd7a27
refactor import map from standard html plugin to node resolve plugin …
thescientist13 Jan 20, 2021
ba8ba75
fix linting
thescientist13 Jan 20, 2021
39ba6f0
remove example foo code
thescientist13 Jan 21, 2021
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
17 changes: 8 additions & 9 deletions greenwood.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const path = require('path');
// const pluginGoogleAnalytics = require('./packages/plugin-google-analytics/src/index');
// const pluginPolyfills = require('./packages/plugin-polyfills/src/index');
const pluginGoogleAnalytics = require('./packages/plugin-google-analytics/src/index');
const pluginPolyfills = require('./packages/plugin-polyfills/src/index');

const META_DESCRIPTION = 'A modern and performant static site generator supporting Web Component based development';
const FAVICON_HREF = '/assets/favicon.ico';
Expand All @@ -20,13 +20,12 @@ module.exports = {
{ rel: 'icon', href: FAVICON_HREF },
{ name: 'google-site-verification', content: '4rYd8k5aFD0jDnN0CCFgUXNe4eakLP4NnA18mNnK5P0' }
],
// TODO
// plugins: [
// ...pluginGoogleAnalytics({
// analyticsId: 'UA-147204327-1'
// }),
// ...pluginPolyfills()
// ],
plugins: [
pluginGoogleAnalytics({
analyticsId: 'UA-147204327-1'
}),
pluginPolyfills()
],
markdown: {
plugins: [
'@mapbox/rehype-prism',
Expand Down
27 changes: 23 additions & 4 deletions packages/cli/src/config/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,28 @@ function greenwoodHtmlPlugin(compilation) {

return {
name: 'greenwood-html-plugin',
load(id) {
if (path.extname(id) === '.html') {
return '';
async load(id) {
const extension = path.extname(id);

if (extension === '.html') {
return Promise.resolve('');
}

// handle custom user file extensions
const customResources = compilation.config.plugins.filter((plugin) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Was curious how we were going to implement this in rollup. This is exactly whats needed.

return plugin.type === 'resource';
}).map((plugin) => {
return plugin.provider(compilation);
}).filter((resource) => {
if (resource.shouldServe(id)) {
return resource;
}
});

if (customResources.length) {
const response = await customResources[0].serve(id);

return response.body;
}
},
// TODO do this during load instead?
Expand All @@ -59,7 +78,7 @@ function greenwoodHtmlPlugin(compilation) {

const srcPath = src.replace('../', './');
const source = fs.readFileSync(path.join(userWorkspace, srcPath), 'utf-8');

that.emitFile({
type: 'chunk',
id: srcPath,
Expand Down
62 changes: 62 additions & 0 deletions packages/cli/src/lib/resource-interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const path = require('path');

class ResourceInterface {
constructor(compilation, options = {}) {
this.compilation = compilation;
this.options = options;
this.extensions = [];
this.contentType = '';
}

// hidden API?
shouldResolve(url) {
const { extensions } = this;

return extensions.length && extensions.length > 0
|| extensions[0] === '*'
|| extensions.indexOf(path.extname(url) >= 0);
}

async resolve(url) {
return Promise.resolve(url);
}

// introduce a new resource type to the browser, on the fly, ex: `<script src="index.ts">`
// eslint-disable-next-line no-unused-vars
shouldServe(url, headers) {
return this.extensions.indexOf(path.extname(url)) >= 0;
}

// eslint-disable-next-line no-unused-vars
async serve(url, headers) {
return Promise.resolve({});
}

// handle an already resolved / served resource
// eslint-disable-next-line no-unused-vars
shouldIntercept(url, headers) {
return false;
}

// eslint-disable-next-line no-unused-vars
async intercept(contents, headers) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting idea +1

return Promise.resolve(contents);
}

// handle a (final) resource type post build, pre optimize,
// ex: remove es shim <script>, convert .ts -> .js and update path references
// this is only an _index.html_ file, BYOA (Bring Your Own AST)
// eslint-disable-next-line no-unused-vars
shouldOptimize(contents, url) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect for post-serialize plugin functionality

return false;
}

// eslint-disable-next-line no-unused-vars
async optimize (contents, url) {
return Promise.resolve(contents);
}
}

module.exports = {
ResourceInterface
};
36 changes: 20 additions & 16 deletions packages/cli/src/lifecycles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,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 } = userCfgFile;
const { workspace, devServer, title, markdown, meta, plugins } = userCfgFile;

// workspace validation
if (workspace) {
Expand Down Expand Up @@ -68,24 +68,29 @@ module.exports = readAndMergeConfig = async() => {
// reject(`Error: provided optimization "${optimization}" is not supported. Please use one of: ${optimizations.join(', ')}.`);
// }

// TODO
// if (plugins && plugins.length > 0) {
// const types = ['index', 'webpack'];
if (plugins && plugins.length > 0) {
const types = ['resource'];

// plugins.forEach(plugin => {
// if (!plugin.type || types.indexOf(plugin.type) < 0) {
// reject(`Error: greenwood.config.js plugins must be one of type "${types.join(', ')}". got "${plugin.type}" instead.`);
// }
plugins.forEach(plugin => {
if (!plugin.type || types.indexOf(plugin.type) < 0) {
reject(`Error: greenwood.config.js plugins must be one of type "${types.join(', ')}". got "${plugin.type}" instead.`);
}

// if (!plugin.provider || typeof plugin.provider !== 'function') {
// const providerTypeof = typeof plugin.provider;
if (!plugin.provider || typeof plugin.provider !== 'function') {
const providerTypeof = typeof plugin.provider;

// reject(`Error: greenwood.config.js plugins provider must of type function. got ${providerTypeof} instead.`);
// }
// });
reject(`Error: greenwood.config.js plugins provider must be a function. got ${providerTypeof} instead.`);
}

// customConfig.plugins = customConfig.plugins.concat(plugins);
// }
if (!plugin.name || typeof plugin.name !== 'string') {
const nameTypeof = typeof plugin.name;

reject(`Error: greenwood.config.js plugins must have a name. got ${nameTypeof} instead.`);
}
});

customConfig.plugins = customConfig.plugins.concat(plugins);
}

if (devServer && Object.keys(devServer).length > 0) {

Expand All @@ -95,7 +100,6 @@ module.exports = readAndMergeConfig = async() => {
reject(`Error: greenwood.config.js devServer port must be an integer. Passed value was: ${devServer.port}`);
} else {
customConfig.devServer.port = devServer.port;
// console.log(`custom port provided => ${customConfig.devServer.port}`);
}
}
}
Expand Down
41 changes: 25 additions & 16 deletions packages/cli/src/lifecycles/serialize.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,53 @@
const BrowserRunner = require('../lib/browser');
const { promises: fsp } = require('fs');
const fs = require('fs');
const path = require('path');
const pluginResourceStandardHtml = require('../plugins/resource/plugin-standard-html');

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

return plugin.type === 'resource'
&& provider.shouldOptimize
&& provider.optimize;
}).map((plugin) => {
return plugin.provider(compilationCopy);
})
];

await browserRunner.init();

const runBrowser = async (serverUrl, pages, outputDir) => {

try {
return Promise.all(pages.map(async(page) => {
const { route } = page;
console.info('serializing page...', route);

return await browserRunner
.serialize(`${serverUrl}${route}`)
.then(async (html) => {
.then(async (indexHtml) => {
const outputPath = `${outputDir}${route}index.html`;

// TODO allow setup / teardown (e.g. module shims, then remove module-shims)
let htmlModified = html;

// TODO should really be happening via plugins or other standardize setup / teardown mechanism
htmlModified = htmlModified.replace(/<script src="\/node_modules\/@webcomponents\/webcomponentsjs\/webcomponents-bundle.js"><\/script>/, '');
htmlModified = htmlModified.replace(/<script type="importmap-shim">.*?<\/script>/s, '');
htmlModified = htmlModified.replace(/<script defer="" src="\/node_modules\/es-module-shims\/dist\/es-module-shims.js"><\/script>/, '');
htmlModified = htmlModified.replace(/<script type="module-shim"/g, '<script type="module"');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 agree with this refactor.


// console.debug('final HTML', htmlModified);
console.info(`Serializing complete for page ${route}.`);
// console.debug(`outputting to... ${outputDir.replace(`${process.cwd()}`, '.')}${outputPath}`);

const htmlOptimized = await optimizeResources.reduce(async (htmlPromise, resource) => {
const html = await htmlPromise;

return resource.shouldOptimize(html)
? resource.optimize(html)
: Promise.resolve(html);
}, Promise.resolve(indexHtml));

if (!fs.existsSync(path.join(outputDir, route))) {
fs.mkdirSync(path.join(outputDir, route), {
recursive: true
});
}

await fsp.writeFile(outputPath, htmlModified);
await fs.promises.writeFile(outputPath, htmlOptimized);
});
}));
} catch (e) {
Expand Down
Loading