Skip to content

Commit

Permalink
feat(plugin-conventions): support defaultShadowOptions in conventions…
Browse files Browse the repository at this point in the history
… support

BREAKING CHANGE: changed options for all bundler plugins.
closes #578
  • Loading branch information
3cp committed Aug 29, 2019
1 parent 1541faf commit dcf0bba
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,69 +21,76 @@ export function getHTMLOnlyElement() {
assert.equal(result.code, expected);
});

it('processes template with no dependencies in ts mode', function () {
const html = '<template></template>';
it('processes template with dependencies', function () {
const html = '<import from="./hello-world.html" /><template><import from="foo"><require from="./foo-bar.scss"></require></template>';
const expected = `import { CustomElement } from '@aurelia/runtime';
import * as h0 from "./hello-world.html";
const d0 = h0.getHTMLOnlyElement();
import * as d1 from "foo";
import "./foo-bar.scss";
export const name = "foo-bar";
export const template = "<template></template>";
export default template;
export const dependencies: any[] = [ ];
let _e: any;
export function getHTMLOnlyElement(): any {
export const dependencies = [ d0, d1 ];
let _e;
export function getHTMLOnlyElement() {
if (!_e) {
_e = CustomElement.define({ name, template, dependencies });
}
return _e;
}
`;
const result = preprocessHtmlTemplate('lo/foo-bar.html', html, true);
const result = preprocessHtmlTemplate('lo\\FooBar.html', html);
assert.equal(result.code, expected);
});

it('processes template with dependencies', function () {
it('processes template with css dependencies in shadowDOM mode', function () {
const html = '<import from="./hello-world.html" /><template><import from="foo"><require from="./foo-bar.scss"></require></template>';
const expected = `import { CustomElement } from '@aurelia/runtime';
import * as h0 from "./hello-world.html";
const d0 = h0.getHTMLOnlyElement();
import * as d1 from "foo";
import "./foo-bar.scss";
import { Registration } from '@aurelia/kernel';
import d2 from "./foo-bar.scss";
export const name = "foo-bar";
export const template = "<template></template>";
export default template;
export const dependencies = [ d0, d1 ];
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
export const shadowOptions = {"mode":"open"};
let _e;
export function getHTMLOnlyElement() {
if (!_e) {
_e = CustomElement.define({ name, template, dependencies });
_e = CustomElement.define({ name, template, dependencies, shadowOptions });
}
return _e;
}
`;
const result = preprocessHtmlTemplate('lo\\FooBar.html', html);
const result = preprocessHtmlTemplate('lo\\FooBar.html', html, { mode: 'open' });
assert.equal(result.code, expected);
});

it('processes template with dependencies in ts mode', function () {
it('processes template with css dependencies in shadowDOM mode with string module wrap', function () {
const html = '<import from="./hello-world.html" /><template><import from="foo"><require from="./foo-bar.scss"></require></template>';
const expected = `import { CustomElement } from '@aurelia/runtime';
import * as h0 from "./hello-world.html";
const d0 = h0.getHTMLOnlyElement();
import * as d1 from "foo";
import "./foo-bar.scss";
import { Registration } from '@aurelia/kernel';
import d2 from "raw-loader!./foo-bar.scss";
export const name = "foo-bar";
export const template = "<template></template>";
export default template;
export const dependencies: any[] = [ d0, d1 ];
let _e: any;
export function getHTMLOnlyElement(): any {
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
export const shadowOptions = {"mode":"closed"};
let _e;
export function getHTMLOnlyElement() {
if (!_e) {
_e = CustomElement.define({ name, template, dependencies });
_e = CustomElement.define({ name, template, dependencies, shadowOptions });
}
return _e;
}
`;
const result = preprocessHtmlTemplate('lo\\FooBar.html', html, true);
const result = preprocessHtmlTemplate('lo\\FooBar.html', html, { mode: 'closed' }, id => `raw-loader!${id}`);
assert.equal(result.code, expected);
});

});
29 changes: 18 additions & 11 deletions packages/__tests__/plugin-conventions/preprocess.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,42 @@ export function getHTMLOnlyElement() {
assert.equal(result.map.version, 3);
});

it('transforms html file in ts mode', function () {
const html = '<template></template>';
it('transforms html file with shadowOptions', function () {
const html = '<import from="./hello-world.html" /><template><import from="foo"><require from="./foo-bar.scss"></require></template>';
const expected = `import { CustomElement } from '@aurelia/runtime';
import * as h0 from "./hello-world.html";
const d0 = h0.getHTMLOnlyElement();
import * as d1 from "foo";
import { Registration } from '@aurelia/kernel';
import d2 from "raw-loader!./foo-bar.scss";
export const name = "foo-bar";
export const template = "<template></template>";
export default template;
export const dependencies: any[] = [ ];
let _e: any;
export function getHTMLOnlyElement(): any {
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
export const shadowOptions = {"mode":"open"};
let _e;
export function getHTMLOnlyElement() {
if (!_e) {
_e = CustomElement.define({ name, template, dependencies });
_e = CustomElement.define({ name, template, dependencies, shadowOptions });
}
return _e;
}
`;
const result = preprocess('src/foo-bar.html', html, true);
const result = preprocess('src/foo-bar.html', html, '', { mode: 'open' }, id => `raw-loader!${id}`);
assert.equal(result.code, expected);
assert.equal(result.map.version, 3);
});

it('does not touch js/ts file without html pair', function () {
const js = `export class Foo {}\n`;
const result = preprocess('src/foo.js', js, false, '', () => false);
const result = preprocess('src/foo.js', js, '', null, null, () => false);
assert.equal(result.code, js);
assert.equal(result.map.version, 3);
});

it('does not touch js/ts file with html pair but wrong resource name', function () {
const js = `export class Foo {}\n`;
const result = preprocess('src/bar.js', js, false, '', () => true);
const result = preprocess('src/bar.js', js, '', null, null, () => true);
assert.equal(result.code, js);
assert.equal(result.map.version, 3);
});
Expand All @@ -67,8 +73,9 @@ export class FooBar {}
const result = preprocess(
path.join('src', 'foo-bar.ts'),
js,
false,
'base',
null,
null,
(filePath: string) => filePath === path.join('base', 'src', 'foo-bar.html')
);
assert.equal(result.code, expected);
Expand Down Expand Up @@ -150,7 +157,7 @@ export class AbcBindingCommand {
@customElement({ ...__fooBarViewDef, dependencies: [ ...__fooBarViewDef.dependencies, LoremCustomAttribute, ForOne, TheSecondValueConverter, SomeBindingBehavior, AbcBindingCommand ] })
export class FooBar {}
`;
const result = preprocess('src/foo-bar.js', js, true, '', () => true);
const result = preprocess('src/foo-bar.js', js, '', null, null, () => true);
assert.equal(result.code, expected);
assert.equal(result.map.version, 3);
});
Expand Down
28 changes: 17 additions & 11 deletions packages/__tests__/plugin-gulp/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import * as v from 'vinyl';
const Vinyl = ((v as any).default || v) as typeof import('vinyl');
type Vinyl = typeof Vinyl.prototype;

function preprocess(filePath: string, contents: string, ts: boolean = false) {
function preprocess(
filePath: string,
contents: string,
basePath: string = '',
defaultShadowOptions: { mode: 'open' | 'closed' } | null = null,
stringModuleWrap: ((id: string) => string) | null = null
) {
return {
code: (ts ? 'ts ' : '') + 'processed ' + filePath + ' ' + contents,
code: 'processed ' + (defaultShadowOptions ? (JSON.stringify(defaultShadowOptions) +
' ') : '') + (defaultShadowOptions && stringModuleWrap ? stringModuleWrap(filePath) : filePath) + ' ' + contents,
map: { version: 3 }
};
}

describe('plugin-gulp', function () {
it('complains about stream mode', function (done) {
const files: Vinyl[] = [];
const t = plugin.call(undefined, false, preprocess);
const t = plugin.call(undefined, null, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand All @@ -38,7 +45,7 @@ describe('plugin-gulp', function () {
it('ignores non js/ts/html file', function (done) {
const css = '.a { color: red; }';
const files: Vinyl[] = [];
const t = plugin.call(undefined, false, preprocess);
const t = plugin.call(undefined, null, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand Down Expand Up @@ -66,7 +73,7 @@ describe('plugin-gulp', function () {
const expected = 'processed src/foo-bar.html content';

const files: Vinyl[] = [];
const t = plugin.call(undefined, false, preprocess);
const t = plugin.call(undefined, null, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand All @@ -89,13 +96,12 @@ describe('plugin-gulp', function () {
}));
});

it('transforms html file in ts mode', function(done) {
it('transforms html file in shadowDOM mode', function(done) {
const content = 'content';
const expected = 'ts processed src/foo-bar.html content';

const expected = 'processed {"mode":"open"} text!src/foo-bar.html content';

const files: Vinyl[] = [];
const t = plugin.call(undefined, true, preprocess);
const t = plugin.call(undefined, { mode: 'open' }, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand Down Expand Up @@ -124,7 +130,7 @@ describe('plugin-gulp', function () {
const expected = 'processed src/foo-bar.js content';

const files: Vinyl[] = [];
const t = plugin.call(undefined, false, preprocess);
const t = plugin.call(undefined, null, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand Down Expand Up @@ -152,7 +158,7 @@ describe('plugin-gulp', function () {
const expected = 'processed src/foo-bar.ts content';

const files: Vinyl[] = [];
const t = plugin.call(undefined, false, preprocess);
const t = plugin.call(undefined, null, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand Down
17 changes: 12 additions & 5 deletions packages/__tests__/webpack-loader/loader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { loader } from '@aurelia/webpack-loader';
import { assert } from '@aurelia/testing';

function preprocess(filePath: string, contents: string, ts: boolean = false) {
function preprocess(
filePath: string,
contents: string,
basePath: string = '',
defaultShadowOptions: { mode: 'open' | 'closed' } | null = null,
stringModuleWrap: ((id: string) => string) | null = null
) {
return {
code: (ts ? 'ts ' : '') + 'processed ' + filePath + ' ' + contents,
code: 'processed ' + (defaultShadowOptions ? (JSON.stringify(defaultShadowOptions) +
' ') : '') + (defaultShadowOptions && stringModuleWrap ? stringModuleWrap(filePath) : filePath) + ' ' + contents,
map: { version: 3 }
};
}
Expand All @@ -29,9 +36,9 @@ describe('webpack-loader', function () {
loader.call(context, content, preprocess);
});

it('transforms html file in ts mode', function(done) {
it('transforms html file in shadowDOM mode', function(done) {
const content = 'content';
const expected = 'ts processed src/foo-bar.html content';
const expected = 'processed {"mode":"open"} raw-loader!src/foo-bar.html content';

const context = {
async: () => function(err, code, map) {
Expand All @@ -43,7 +50,7 @@ describe('webpack-loader', function () {
assert.equal(map.version, 3);
done();
},
query: { ts: true },
query: { defaultShadowOptions: { mode: 'open' } },
resourcePath: 'src/foo-bar.html'
};

Expand Down
45 changes: 33 additions & 12 deletions packages/plugin-conventions/src/preprocess-html-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,43 @@ import * as path from 'path';
import { fileBase } from './file-base';
import { stripHtmlImport } from './strip-html-import';

export function preprocessHtmlTemplate(filePath: string, rawHtml: string, ts: boolean = false) {
// stringModuleWrap is to deal with pure css text module import in shadowDOM mode.
// For webpack:
// import d0 from 'raw-loader!./foo.css';
// For dumber/requirejs:
// import d0 from 'text!./foo.css';
// We cannot use
// import d0 from './foo.css';
// because most bundler by default will inject that css into HTML head.
export function preprocessHtmlTemplate(filePath: string, rawHtml: string, defaultShadowOptions?: { mode: 'open' | 'closed' }, stringModuleWrap?: (id: string) => string) {
const { html, deps } = stripHtmlImport(rawHtml);

const viewDeps: string[] = [];
const importStatements: string[] = [];
let registrationImported = false;

deps.forEach((d, i) => {
const ext = path.extname(d);
let importStatement: string;

if (isCss(ext)) {
importStatement = `import ${s(d)};\n`;
if (defaultShadowOptions) {
if (!registrationImported) {
importStatements.push(`import { Registration } from '@aurelia/kernel';\n`);
registrationImported = true;
}
const stringModuleId = stringModuleWrap ? stringModuleWrap(d) : d;
importStatements.push(`import d${i} from ${s(stringModuleId)};\n`);
viewDeps.push(`Registration.defer('.css', d${i})`);
} else {
importStatements.push(`import ${s(d)};\n`);
}
} else if (ext === '.html') {
importStatement = `import * as h${i} from ${s(d)};\n`;
importStatement += `const d${i} = h${i}.getHTMLOnlyElement();\n`;
importStatements.push(`import * as h${i} from ${s(d)};\nconst d${i} = h${i}.getHTMLOnlyElement();\n`);
viewDeps.push(`d${i}`);
} else {
importStatement = `import * as d${i} from ${s(d)};\n`;
importStatements.push(`import * as d${i} from ${s(d)};\n`);
viewDeps.push(`d${i}`);
}

importStatements.push(importStatement);
});

const name = kebabCase(fileBase(filePath));
Expand All @@ -35,11 +50,17 @@ export function preprocessHtmlTemplate(filePath: string, rawHtml: string, ts: bo
m.append(`export const name = ${s(name)};
export const template = ${s(html)};
export default template;
export const dependencies${ts ? ': any[]' : ''} = [ ${viewDeps.join(', ')} ];
let _e${ts ? ': any' : ''};
export function getHTMLOnlyElement()${ts ? ': any' : ''} {
export const dependencies = [ ${viewDeps.join(', ')} ];
`);

if (defaultShadowOptions) {
m.append(`export const shadowOptions = ${JSON.stringify(defaultShadowOptions)};\n`);
}

m.append(`let _e;
export function getHTMLOnlyElement() {
if (!_e) {
_e = CustomElement.define({ name, template, dependencies });
_e = CustomElement.define({ name, template, dependencies${defaultShadowOptions ? ', shadowOptions' : ''} });
}
return _e;
}
Expand Down
7 changes: 5 additions & 2 deletions packages/plugin-conventions/src/preprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ export function preprocess(
// The filePath is used in sourceMap.
filePath: string,
contents: string,
ts: boolean = false,
// The base file path that filePath is related to. Used for checking existence of html pair.
// We separated filePath and basePath because filePath will be written into source map.
basePath: string = '',
defaultShadowOptions: { mode: 'open' | 'closed' } | null = null,
// More details in ./preprocess-html-template.ts
stringModuleWrap: ((id: string) => string) | null = null,
// For testing
_fileExists = fileExists
) {
const ext = path.extname(filePath);

if (ext === '.html') {
return preprocessHtmlTemplate(filePath, contents, ts);
return preprocessHtmlTemplate(filePath, contents, defaultShadowOptions || undefined, stringModuleWrap || undefined);
} else {
const htmlFilePath = path.join(basePath, filePath.slice(0, - ext.length) + '.html');
const hasHtmlPair = _fileExists(htmlFilePath);
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-conventions/src/strip-html-import.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { parseFragment, DefaultTreeElement, ElementLocation } from 'parse5';

interface strippedHtml {
interface IStrippedHtml {
html: string,
deps: string[]
}

export function stripHtmlImport(rawHtml: string): strippedHtml {
export function stripHtmlImport(rawHtml: string): IStrippedHtml {
const deps: string[] = [];
const toRemove: [number, number][] = [];
const tree = parseFragment(rawHtml, { sourceCodeLocationInfo: true });
Expand Down
Loading

0 comments on commit dcf0bba

Please sign in to comment.