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

[DO NOT MERGE] Rfc/issue 354 strict mode inline script poc #401

Closed
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b920218
enable spa mode and ssg by default
thescientist13 Jul 18, 2020
6e71ce3
async script loading for SPA mode
thescientist13 Jul 18, 2020
a64d66e
rename ssg to strict mode and made spa mode default
thescientist13 Jul 23, 2020
cc45911
set spa as default mode
thescientist13 Jul 23, 2020
24e6045
use defer tag
thescientist13 Jul 23, 2020
eb678ba
async smoke test
thescientist13 Jul 23, 2020
342e136
test with strict mode
thescientist13 Jul 25, 2020
59c28b0
commit and how it works docs
thescientist13 Jul 29, 2020
d873e66
spa mode TODO tracking documentation
thescientist13 Jul 30, 2020
b70ae8d
make strict default and add unit tests
thescientist13 Jul 31, 2020
4010bcf
fix mode check and set default to strict
thescientist13 Aug 1, 2020
e72ca70
add error test case for mode
thescientist13 Aug 1, 2020
5a4d24a
remove describe.only
thescientist13 Aug 1, 2020
58571f5
remove debugging
thescientist13 Aug 1, 2020
87d66a5
strict mode as default for test cases
thescientist13 Aug 1, 2020
85bdd59
remove duplicate it
thescientist13 Aug 1, 2020
7288ebe
test case for spa mode
thescientist13 Aug 1, 2020
78afc28
refactoring out page bundles in strict mode
thescientist13 Aug 2, 2020
cece625
strip out lit routes
thescientist13 Aug 2, 2020
996c332
use defer
thescientist13 Aug 3, 2020
0e84045
spa mode is default optimization
thescientist13 Aug 5, 2020
f53b859
harden tests for script tags
thescientist13 Aug 6, 2020
28dd2f2
refactor mode spec to test for strict
thescientist13 Aug 6, 2020
e186f10
rename mode to optimization and defer to async
thescientist13 Aug 6, 2020
c2c669e
update docs
thescientist13 Aug 6, 2020
a11e092
inline javascript in strict mode POC
thescientist13 Aug 6, 2020
620b66e
Merge branch 'master' into rfc/issue-354-strict-mode-inline-script-poc
thescientist13 Aug 6, 2020
ad71e29
Merge branch 'master' into rfc/issue-354-strict-mode-inline-script-poc
thescientist13 Aug 8, 2020
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 @@ -6,6 +6,7 @@ const META_DESCRIPTION = 'A modern and performant static site generator supporti
const FAVICON_HREF = '/assets/favicon.ico';

module.exports = {
optimization: 'strict',
workspace: path.join(__dirname, 'www'),
title: 'Greenwood',
meta: [
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/data/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const backupQuery = client.query;

client.query = (params) => {
if (APOLLO_STATE) {
// __APOLLO_STATE__ defined, in "SSG" mode...
// __APOLLO_STATE__ defined, in production mode
const queryHash = getQueryHash(params.query, params.variables);
const cachePath = `/${queryHash}-cache.json`;

Expand All @@ -27,7 +27,7 @@ client.query = (params) => {
};
});
} else {
// __APOLLO_STATE__ NOT defined, in "SPA" mode
// __APOLLO_STATE__ NOT defined, in development mode
return backupQuery(params);
}
};
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/data/queries/config.gql
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ query {
value,
href
},
optimization,
publicPath,
title,
workspace
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/data/schema/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const configTypeDefs = gql`
type Config {
devServer: DevServer,
meta: [Meta],
optimization: String,
publicPath: String,
title: String,
workspace: String
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/src/lifecycles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ const fs = require('fs-extra');
const path = require('path');
const url = require('url');

const optimizations = ['strict', 'spa'];

let defaultConfig = {
workspace: path.join(process.cwd(), 'src'),
devServer: {
port: 1984,
host: 'localhost'
},
optimization: 'spa',
publicPath: '/',
title: 'My App',
meta: [],
Expand All @@ -25,7 +28,7 @@ module.exports = readAndMergeConfig = async() => {

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

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

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 = ['index', 'webpack'];

Expand Down
24 changes: 19 additions & 5 deletions packages/cli/src/lifecycles/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,27 @@ module.exports = serializeBuild = async (compilation) => {

return await browserRunner.serialize(`http://127.0.0.1:${PORT}${route}`).then(async (content) => {
const target = path.join(publicDir, route);
const html = content
.replace(polyfill, '')
.replace('<script></script>', `
<script data-state="apollo">
const optimization = compilation.config.optimization;
const isStrictOptimization = optimization === 'strict';
const apolloScript = isStrictOptimization
? ''
: `<script data-state="apollo">
window.__APOLLO_STATE__ = true;
</script>
`);
`;

let html = content
.replace(polyfill, '')
.replace('<script></script>', apolloScript);

if (isStrictOptimization) { // no javascript
html = html.replace(/<script type="text\/javascript" src="\/index.*.bundle\.js"><\/script>/, ''); // main bundle
html = html.replace(/<script charset="utf-8" src="\/*.*.bundle\.js"><\/script>/, ''); // dynamic / import() bundles
html = html.replace(/<lit-route path="(.*)" component="(.*)" class="(.*)">(.*)<\/lit-route>/g, ''); // lit redux routes
} else if (optimization === 'spa') { // all the javascript, and async!
html = html.replace(/<script type="text\/javascript"/, '<script async="" type="text/javascript"');
html = html.replace(/<script charset="utf-8"/, '<script async="" charset="utf-8"'); // dynamic / import() bundles
}

await fs.mkdirs(target, { recursive: true });
await fs.writeFile(path.join(target, 'index.html'), html);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Use Case
* Run Greenwood build command with a bad value for optimization in a custom config.
*
* User Result
* Should throw an error.
*
* User Command
* greenwood build
*
* User Config
* {
* optimization: 'lorumipsum'
* }
*
* User Workspace
* Greenwood default
*/
const expect = require('chai').expect;
const TestBed = require('../../../../../test/test-bed');

describe('Build Greenwood With: ', function() {
let setup;

before(async function() {
setup = new TestBed();
await setup.setupTestBed(__dirname);
});

describe('Custom Configuration with a bad value for Optimization', function() {
it('should throw an error that provided optimization is not valid', async function() {
try {
await setup.runGreenwoodCommand('build');
} catch (err) {
expect(err).to.contain('Error: provided optimization "loremipsum" is not supported. Please use one of: strict, spa.');
}
});
});

after(function() {
setup.teardownTestBed();
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
optimization: 'loremipsum'
};
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('Build Greenwood With: ', function() {
});

it('should have one <script> tag in the <body> for the main bundle', function() {
const scriptTags = dom.window.document.querySelectorAll('body script');
const scriptTags = dom.window.document.querySelectorAll('body > script');
const bundledScript = Array.prototype.slice.call(scriptTags).filter(script => {
const src = script.src.replace('file:///', '');

Expand All @@ -93,6 +93,32 @@ describe('Build Greenwood With: ', function() {
expect(bundledScript.length).to.be.equal(1);
});

it('should have one <script> tag in the <body> for the main bundle loaded with async', function() {
const scriptTags = dom.window.document.querySelectorAll('body > script');
const bundledScript = Array.prototype.slice.call(scriptTags).filter(script => {
const src = script.src.replace('file:///', '');

return mainBundleScriptRegex.test(src);
});

expect(bundledScript[0].getAttribute('async')).to.be.equal('');
});

it('should have one <script> tag for Apollo state', function() {
const scriptTags = dom.window.document.querySelectorAll('script');
const bundleScripts = Array.prototype.slice.call(scriptTags).filter(script => {
return script.getAttribute('data-state') === 'apollo';
});

expect(bundleScripts.length).to.be.equal(1);
});

it('should have only one <script> tag in the <head>', function() {
const scriptTags = dom.window.document.querySelectorAll('head > script');

expect(scriptTags.length).to.be.equal(1);
});

it('should have a router outlet tag in the <body>', function() {
const outlet = dom.window.document.querySelectorAll('body eve-app');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Use Case
* Run Greenwood with optimization setting in Greenwood config set to strict.
*
* User Result
* Should generate a bare bones Greenwood build with bundle JavaScript and routes.
*
* User Command
* greenwood build
*
* User Config
* {
* optimization: 'spa'
* }
*
* User Workspace
* Greenwood default w/ nested page
* src/
* pages/
* about/
* index.md
* hello.md
* index.md
*/
const { JSDOM } = require('jsdom');
const path = require('path');
const expect = require('chai').expect;
const runSmokeTest = require('../../../../../test/smoke-test');
const TestBed = require('../../../../../test/test-bed');

describe('Build Greenwood With: ', function() {
const LABEL = 'Custom Mode';
let setup;

before(async function() {
setup = new TestBed();
this.context = await setup.setupTestBed(__dirname);
});

describe(LABEL, function() {

before(async function() {
await setup.runGreenwoodCommand('build');
});

runSmokeTest(['public', 'not-found', 'hello'], LABEL);

describe('Strict', function() {
let dom;

beforeEach(async function() {
dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, 'index.html'));
});

it('should have no <script> tag in the <body>', function() {
const scriptTags = dom.window.document.querySelectorAll('body > script');

expect(scriptTags.length).to.be.equal(0);
});

it('should have no <script> tags in the <head>', function() {
const scriptTags = dom.window.document.querySelectorAll('head > script');

expect(scriptTags.length).to.be.equal(0);
});

it('should have no <script> tags for Apollo state', function() {
const scriptTags = dom.window.document.querySelectorAll('script');
const bundleScripts = Array.prototype.slice.call(scriptTags).filter(script => {
return script.getAttribute('data-state') === 'apollo';
});

expect(bundleScripts.length).to.be.equal(0);
});

it('should have a router outlet tag in the <body>', function() {
const outlet = dom.window.document.querySelectorAll('body eve-app');

expect(outlet.length).to.be.equal(1);
});

it('should have only 2 route tags in the <body>', function() {
const routes = dom.window.document.querySelectorAll('body lit-route');

expect(routes.length).to.be.equal(2);
});
});

});

after(function() {
setup.teardownTestBed();
});

});
3 changes: 3 additions & 0 deletions packages/cli/test/cases/build.config.mode/greenwood.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
optimization: 'strict'
};
Empty file.
7 changes: 7 additions & 0 deletions packages/cli/test/cases/build.config.mode/src/pages/hello.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
label: 'hello'
title: 'Hello Page'
---
### Hello World

This is an example page built by Greenwood. Make your own in _src/pages_!
3 changes: 3 additions & 0 deletions packages/cli/test/cases/build.config.mode/src/pages/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Greenwood

This is the home page built by Greenwood. Make your own pages in src/pages/index.js!
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ describe('Build Greenwood With: ', function() {
expect(title).to.be.equal(configTitle);
});

// rest of index smoke-test because <title></title> is changed for this case
it('should have one <script> tag in the <body> for the main bundle', function() {
const scriptTags = dom.window.document.querySelectorAll('body script');
const scriptTags = dom.window.document.querySelectorAll('body > script');
const bundledScript = Array.prototype.slice.call(scriptTags).filter(script => {
const src = script.src.replace('file:///', '');

Expand All @@ -75,6 +74,32 @@ describe('Build Greenwood With: ', function() {

expect(bundledScript.length).to.be.equal(1);
});

it('should have one <script> tag in the <body> for the main bundle loaded with async', function() {
const scriptTags = dom.window.document.querySelectorAll('body > script');
const bundledScript = Array.prototype.slice.call(scriptTags).filter(script => {
const src = script.src.replace('file:///', '');

return mainBundleScriptRegex.test(src);
});

expect(bundledScript[0].getAttribute('async')).to.be.equal('');
});

it('should have one <script> tag for Apollo state', function() {
const scriptTags = dom.window.document.querySelectorAll('script');
const bundleScripts = Array.prototype.slice.call(scriptTags).filter(script => {
return script.getAttribute('data-state') === 'apollo';
});

expect(bundleScripts.length).to.be.equal(1);
});

it('should have only one <script> tag in the <head>', function() {
const scriptTags = dom.window.document.querySelectorAll('head > script');

expect(scriptTags.length).to.be.equal(1);
});

it('should have a router outlet tag in the <body>', function() {
const outlet = dom.window.document.querySelectorAll('body eve-app');
Expand Down
Loading