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

Bug/issue 431 template <head> tags merge order #475

Merged
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
4 changes: 2 additions & 2 deletions packages/cli/src/config/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,13 @@ function greenwoodHtmlPlugin(compilation) {
const parsedAttributes = parseTagForAttributes(scriptTag);

// handle <script type="module"> /* inline code */ </script>
if (parsedAttributes.type === 'module' && scriptTag.rawText !== '') {
if (parsedAttributes.type === 'module' && !parsedAttributes.src) {
for (const innerBundleId of Object.keys(bundles)) {
if (innerBundleId.indexOf(`-${tokenSuffix}`) > 0 && path.extname(innerBundleId) === '.js') {
const bundledSource = fs.readFileSync(path.join(outputDir, innerBundleId), 'utf-8')
.replace(/\.\//g, '/'); // force absolute paths

html = html.replace(scriptTag.rawText, bundledSource);

scratchFiles[innerBundleId] = true;
}
}
Expand Down
51 changes: 39 additions & 12 deletions packages/cli/src/plugins/resource/plugin-standard-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,14 @@ const getPageTemplate = (barePath, workspace, template) => {

const getAppTemplate = (contents, userWorkspace) => {

const appTemplate = `${userWorkspace}/templates/app.html`;
let appTemplateContents = '';
function sliceTemplate(template, pos, needle, replacer) {
return template.slice(0, pos) + template.slice(pos).replace(needle, replacer);
}

const appTemplatePath = `${userWorkspace}/templates/app.html`;
let appTemplateContents = contents || '';

if (fs.existsSync(appTemplate)) {
appTemplateContents = fs.readFileSync(appTemplate, 'utf-8');
if (fs.existsSync(appTemplatePath)) {
const root = htmlparser.parse(contents, {
script: true,
style: true,
Expand All @@ -57,18 +60,24 @@ const getAppTemplate = (contents, userWorkspace) => {
const body = root.querySelector('body').innerHTML;
const headScripts = root.querySelectorAll('head script');
const headLinks = root.querySelectorAll('head link');
const headStyles = root.querySelectorAll('head style');

appTemplateContents = fs.readFileSync(appTemplatePath, 'utf-8');
appTemplateContents = appTemplateContents.replace(/<page-outlet><\/page-outlet>/, body);
headScripts.forEach(script => {

headScripts.forEach((script) => {
const matchNeedle = '</script>';
const matchPos = appTemplateContents.lastIndexOf(matchNeedle);

if (script.rawAttrs !== '') {
appTemplateContents = appTemplateContents.replace(/<\/script>/, `
appTemplateContents = sliceTemplate(appTemplateContents, matchPos, matchNeedle, `
</script>\n
<script ${script.rawAttrs}></script>\n
`);
}

if (script.rawAttrs === '') {
appTemplateContents = appTemplateContents.replace(/<\/script>/, `
appTemplateContents = sliceTemplate(appTemplateContents, matchPos, matchNeedle, `
</script>\n
<script>
${script.text}
Expand All @@ -77,15 +86,33 @@ const getAppTemplate = (contents, userWorkspace) => {
}
});

headLinks.forEach(link => {
appTemplateContents = appTemplateContents.replace(/<\/link>/, `
</link>\n
<link ${link.rawAttrs}></link>\n
headLinks.forEach((link) => {
const matchNeedle = /<link .*/g;
const matches = appTemplateContents.match(matchNeedle);
const lastLink = matches[matches.length - 1];

appTemplateContents = appTemplateContents.replace(lastLink, `
${lastLink}\n
<link ${link.rawAttrs}/>
`);
});

headStyles.forEach((style) => {
const matchNeedle = '</style>';
const matchPos = appTemplateContents.lastIndexOf(matchNeedle);

if (style.rawAttrs === '') {
appTemplateContents = sliceTemplate(appTemplateContents, matchPos, matchNeedle, `
</style>\n
<style>
${style.text}
</style>\n
`);
}
});
}

return appTemplateContents || contents;
return appTemplateContents;
};

const getUserScripts = (contents) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Use Case
* Run Greenwood build command with no config and custom page (and app) template.
*
* User Result
* Should generate a bare bones Greenwood build with custom page template.
*
* User Command
* greenwood build
*
* User Config
* None (Greenwood Default)
*
* User Workspace
* src/
* scripts/
* app-template-one.js
* app-template-two.js
* page-template-one.js
* page-template-two.js
* styles/
* app-template-one.css
* app-template-two.css
* page-template-one.css
* page-template-two.css
* templates/
* app.html
* page.html
*/
const expect = require('chai').expect;
const fs = require('fs');
const { JSDOM } = require('jsdom');
const path = require('path');
const TestBed = require('../../../../../test/test-bed');

describe('Build Greenwood With: ', function() {
const LABEL = 'Default Greenwood Configuration and Workspace w/Custom App and Page Templates';
let setup;

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

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

// TODO runSmokeTest(['not-found'], LABEL);

describe('Custom App and Page Templates', function() {
let dom;

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

it('should output a single index.html file using our custom page template', function() {
expect(fs.existsSync(path.join(this.context.publicDir, './index.html'))).to.be.true;
});

it('should have the specific element we added as part of our custom page template', function() {
const customElement = dom.window.document.querySelectorAll('div.owen-test');

expect(customElement.length).to.equal(1);
});

describe('merge order for app and page template <head> tags', function() {
let scriptTags;
let linkTags;
let styleTags;

before(function() {
scriptTags = dom.window.document.querySelectorAll('head > script');
linkTags = dom.window.document.querySelectorAll('head > link');
styleTags = dom.window.document.querySelectorAll('head > style');
});

it('should have 4 <script> tags in the <head>', function() {
expect(scriptTags.length).to.equal(4);
});

it('should have 4 <link> tags in the <head>', function() {
expect(linkTags.length).to.equal(4);
});

it('should have 5 <style> tags in the <head> (4 + one from Puppeteer)', function() {
expect(styleTags.length).to.equal(5);
});

it('should merge page template <script> tags after app template <script> tags', function() {
expect(scriptTags[0].src).to.match(/app-template-one.*.js/);
expect(scriptTags[1].src).to.match(/app-template-two.*.js/);
expect(scriptTags[2].src).to.match(/page-template-one.*.js/);
expect(scriptTags[3].src).to.match(/page-template-two.*.js/);

scriptTags.forEach((scriptTag) => {
expect(scriptTag.type).to.equal('module');
});
});

it('should merge page template <link> tags after app template <link> tags', function() {
expect(linkTags[0].href).to.match(/app-template-one.*.css/);
expect(linkTags[1].href).to.match(/app-template-two.*.css/);
expect(linkTags[2].href).to.match(/page-template-one.*.css/);
expect(linkTags[3].href).to.match(/page-template-two.*.css/);

linkTags.forEach((linkTag) => {
expect(linkTag.rel).to.equal('stylesheet');
});
});

it('should merge page template <style> tags after app template <style> tags', function() {
// offset index by one since first <style> tag is from Puppeteer
expect(styleTags[1].textContent).to.contain('app-template-one-style');
expect(styleTags[2].textContent).to.contain('app-template-two-style');
expect(styleTags[3].textContent).to.contain('page-template-one-style');
expect(styleTags[4].textContent).to.contain('page-template-two-style');
});
});
});

});

after(function() {
setup.teardownTestBed();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'app template one';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'app template two';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'page template one';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'page template two';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
p {
color: 'royal-blue'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
font-family: 'Comic Sans'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a {
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h1 {
font-size: 16rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" prefix="og:http://ogp.me/ns#">

<head>
<script type="module" src="/scripts/app-template-one.js"></script>
<script type="module" src="/scripts/app-template-two.js"></script>

<link rel="stylesheet" href="/styles/app-template-one.css">
<link rel="stylesheet" href="/styles/app-template-two.css"></link>

<style>
/* app-template-one-style */
span {
text-align: center;
}
</style>
<style>
/* app-template-two-style */
p {
margin: 0 auto;
}
</style>
</head>

<body>
<page-outlet></page-outlet>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en" prefix="og:http://ogp.me/ns#">

<head>
<script type="module" src="../scripts/page-template-one.js"></script>
<script type="module" src="../scripts/page-template-two.js"></script>

<link rel="stylesheet" href="/styles/page-template-one.css"></link>
<link rel="stylesheet" href="/styles/page-template-two.css"/>

<style>
/* page-template-one-style */
ol {
list-style: none;
}
</style>
<style>
/* page-template-two-style */
h3 {
text-decoration: underline;
}
</style>
</head>

<body>

<div class='wrapper'>

<div class='page-template blog-content content owen-test'>
<content-outlet></content-outlet>
</div>

</div>

</body>

</html>
Loading