Skip to content

Commit

Permalink
feat(plugin-conventions): always wrap others resources in defer
Browse files Browse the repository at this point in the history
Wrap other resources (not .html/.js/.ts) in Registration.defer, this will help CSSModule usage in html template.
  • Loading branch information
3cp committed Aug 29, 2019
1 parent 9b8e7f1 commit 082b83b
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ export function getHTMLOnlyElement() {
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) ];
let _e;
export function getHTMLOnlyElement() {
if (!_e) {
Expand All @@ -44,6 +45,30 @@ export function getHTMLOnlyElement() {
assert.equal(result.code, expected);
});

it('processes template with dependencies, wrap css module id', 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 "./foo-bar.scss";
export const name = "foo-bar";
export const template = "<template></template>";
export default template;
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
let _e;
export function getHTMLOnlyElement() {
if (!_e) {
_e = CustomElement.define({ name, template, dependencies });
}
return _e;
}
`;
const result = preprocessHtmlTemplate('lo\\FooBar.html', html, undefined, id => `raw-loader!${id}`);
assert.equal(result.code, expected);
});

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';
Expand Down Expand Up @@ -151,11 +176,12 @@ console.warn("WARN: ShadowDOM is disabled for lo\\\\foo.html. ShadowDOM requires
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";
export const template = "<template></template>";
export default template;
export const dependencies = [ d0, d1 ];
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
let _e;
export function getHTMLOnlyElement() {
if (!_e) {
Expand Down
70 changes: 64 additions & 6 deletions packages/__tests__/plugin-gulp/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function preprocess(
describe('plugin-gulp', function () {
it('complains about stream mode', function (done) {
const files: Vinyl[] = [];
const t = plugin.call(undefined, null, preprocess);
const t = plugin.call(undefined, null, false, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand All @@ -45,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, null, preprocess);
const t = plugin.call(undefined, null, false, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand Down Expand Up @@ -73,7 +73,7 @@ describe('plugin-gulp', function () {
const expected = 'processed src/foo-bar.html content';

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

const files: Vinyl[] = [];
const t = plugin.call(undefined, { mode: 'open' }, preprocess);
const t = plugin.call(undefined, { mode: 'open' }, false, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
files.push(file);
cb();
}
}));
t.on('error', done);
t.on('end', () => {
assert.equal(files.length, 1);
assert.equal(files[0].relative, 'src/foo-bar.html.js');
assert.equal(files[0].contents.toString(), expected);
assert.equal(files[0].sourceMap.version, 3);
done();
});

t.end(new Vinyl({
path: 'src/foo-bar.html',
contents: Buffer.from(content),
sourceMap: {}
}));
});

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

const files: Vinyl[] = [];
const t = plugin.call(undefined, null, true, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
files.push(file);
cb();
}
}));
t.on('error', done);
t.on('end', () => {
assert.equal(files.length, 1);
assert.equal(files[0].relative, 'src/foo-bar.html.js');
assert.equal(files[0].contents.toString(), expected);
assert.equal(files[0].sourceMap.version, 3);
done();
});

t.end(new Vinyl({
path: 'src/foo-bar.html',
contents: Buffer.from(content),
sourceMap: {}
}));
});

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

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

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

const files: Vinyl[] = [];
const t = plugin.call(undefined, null, preprocess);
const t = plugin.call(undefined, null, false, preprocess);
t.pipe(new Writable({
objectMode: true,
write(file: Vinyl, enc, cb) {
Expand Down
41 changes: 41 additions & 0 deletions packages/__tests__/webpack-loader/loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,47 @@ describe('webpack-loader', function () {
loader.call(context, content, preprocess);
});

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

const context = {
async: () => function(err, code, map) {
if (err) {
done(err);
return;
}
assert.equal(code, expected);
assert.equal(map.version, 3);
done();
},
query: { useCSSModule: true },
resourcePath: 'src/foo-bar.html'
};

loader.call(context, content, preprocess);
});
it('transforms html file in shadowDOM mode + CSSModule mode', function(done) {
const content = 'content';
const expected = 'processed {"mode":"open"} src/foo-bar.html content';

const context = {
async: () => function(err, code, map) {
if (err) {
done(err);
return;
}
assert.equal(code, expected);
assert.equal(map.version, 3);
done();
},
query: { defaultShadowOptions: { mode: 'open' }, useCSSModule: true },
resourcePath: 'src/foo-bar.html'
};

loader.call(context, content, preprocess);
});

it('transforms js file', function(done) {
const content = 'content';
const expected = 'processed src/foo-bar.js content';
Expand Down
25 changes: 11 additions & 14 deletions packages/plugin-conventions/src/preprocess-html-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,19 @@ export function preprocessHtmlTemplate(filePath: string, rawHtml: string, defaul

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

if (isCss(ext)) {
if (shadowMode) {
if (!registrationImported) {
statements.push(`import { Registration } from '@aurelia/kernel';\n`);
registrationImported = true;
}
const stringModuleId = stringModuleWrap ? stringModuleWrap(d) : d;
statements.push(`import d${i} from ${s(stringModuleId)};\n`);
viewDeps.push(`Registration.defer('.css', d${i})`);
} else {
statements.push(`import ${s(d)};\n`);
}
} else if (ext === '.html') {
if (ext === '.html') {
statements.push(`import * as h${i} from ${s(d)};\nconst d${i} = h${i}.getHTMLOnlyElement();\n`);
viewDeps.push(`d${i}`);
} else if (ext && ext !== '.js' && ext !== '.ts') {
// Wrap all other unknown resources (including .css, .scss) in defer.
if (!registrationImported) {
statements.push(`import { Registration } from '@aurelia/kernel';\n`);
registrationImported = true;
}
const isCssResource = isCss(ext);
const stringModuleId = isCssResource && shadowMode && stringModuleWrap ? stringModuleWrap(d) : d;
statements.push(`import d${i} from ${s(stringModuleId)};\n`);
viewDeps.push(`Registration.defer('${isCssResource ? '.css' : ext}', d${i})`);
} else {
statements.push(`import * as d${i} from ${s(d)};\n`);
viewDeps.push(`d${i}`);
Expand Down
12 changes: 9 additions & 3 deletions packages/plugin-gulp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ gulp.src('src/**/*.js')
.pipe(babel()); // demo js file with babel here

// For html files
// For apps want to use ShadowDOM
// For apps want to use ShadowDOM or CSSModule
// available defaultShadowOptions are { mode: 'open' }, or { mode: 'closed' }, or null (default).
// by default, option useCSSModule is false. https://github.com/css-modules/css-modules
// Normally you would not use ShadowDOM and CSSModule together, but our tooling doesn't prevent you doing that.
gulp.src('src/**/*.html')
.pipe(au2({defaultShadowOptions: {mode: 'open'}}));
.pipe(au2({defaultShadowOptions: {mode: 'open'}, useCSSModule: false}));

// For apps don't want to use ShadowDOM
// For apps don't want to use ShadowDOM or CSSModule
gulp.src('src/**/*.html')
.pipe(au2());
```
Expand All @@ -56,3 +58,7 @@ declare module '*.html' {
export function getHTMLOnlyElement();
}
```

Note: for CSSModule, there are more configuration to be done in webpack config and app main entry.

TODO: add more info for using CSSModule
18 changes: 15 additions & 3 deletions packages/plugin-gulp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import { preprocess } from '@aurelia/plugin-conventions';

export default function(options: any = {}) {
let shadowOptions;
let useCSSModule = false;
if (options && options.defaultShadowOptions) {
shadowOptions = options.defaultShadowOptions as { mode: 'open' | 'closed' };
}
return plugin(shadowOptions);
if (options && options.useCSSModule) {
useCSSModule = options.useCSSModule;
}
return plugin(shadowOptions, useCSSModule);
}

export function plugin(
shadowOptions?: { mode: 'open' | 'closed' } | null,
shadowOptions?: { mode: 'open' | 'closed' },
useCSSModule?: boolean,
_preprocess = preprocess // for testing
) {
return new Transform({
Expand All @@ -23,7 +28,14 @@ export function plugin(
const { extname } = file;
if (extname === '.html' || extname === '.js' || extname === '.ts') {
// Rewrite foo.html to foo.html.js
const result = _preprocess(file.relative, file.contents.toString(), file.base, shadowOptions, stringModuleWrap);
// Don't wrap css module id when using CSSModule
const result = _preprocess(
file.relative,
file.contents.toString(),
file.base,
shadowOptions,
useCSSModule ? undefined : stringModuleWrap
);
if (extname === '.html') {
file.basename += '.js';
}
Expand Down
23 changes: 20 additions & 3 deletions packages/webpack-loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,23 @@ module: {
{ test: /\.js$/i, use: ['babel-loader', '@aurelia/webpack-loader'], exclude: /node_modules/ },
// For apps in TypeScript with ts-loader
{ test: /\.ts$/i, use: ['ts-loader', '@aurelia/webpack-loader'], exclude: /node_modules/ },
// For apps don't want to use ShadowDOM
// For apps don't want to use ShadowDOM or CSSModule
{ test: /\.html$/i, use: '@aurelia/webpack-loader', exclude: /node_modules/ }
// For apps want to use ShadowDOM
// For apps want to use ShadowDOM or CSSModule
// available defaultShadowOptions are { mode: 'open' }, or { mode: 'closed' }, or null (default).
{ test: /\.html$/i, use: { loader: '@aurelia/webpack-loader', options: { defaultShadowOptions: { mode: 'open' } } }, exclude: /node_modules/ }
// by default, option useCSSModule is false. https://github.com/css-modules/css-modules
// Normally you would not use ShadowDOM and CSSModule together, but our tooling doesn't prevent you doing that.
{
test: /\.html$/i,
use: {
loader: '@aurelia/webpack-loader',
options: {
defaultShadowOptions: { mode: 'open' },
useCSSModule: false
}
},
exclude: /node_modules/
}
]
}
```
Expand All @@ -53,3 +65,8 @@ declare module '*.html' {
export function getHTMLOnlyElement();
}
```

Note: for CSSModule, there are more configuration to be done in webpack config and app main entry.

TODO: add more info for using CSSModule

14 changes: 12 additions & 2 deletions packages/webpack-loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,26 @@ export function loader(
const cb = this.async() as webpack.loader.loaderCallback;
const options = getOptions(this);
let shadowOptions;
let useCSSModule = false;
if (options && options.defaultShadowOptions) {
shadowOptions = options.defaultShadowOptions as { mode: 'open' | 'closed' };
}
if (options && options.useCSSModule) {
useCSSModule = options.useCSSModule;
}
const filePath = this.resourcePath;
const ext = path.extname(filePath);

try {
if (ext === '.html' || ext === '.js' || ext === '.ts') {
const result = _preprocess(filePath, contents, '', shadowOptions, stringModuleWrap);

// Don't wrap css module id when using CSSModule
const result = _preprocess(
filePath,
contents,
'',
shadowOptions,
useCSSModule ? undefined : stringModuleWrap
);
// webpack uses source-map 0.6.1 typings for RawSourceMap which
// contains typing error version: string (should be number).
// use result.map as any to bypass the typing issue.
Expand Down

0 comments on commit 082b83b

Please sign in to comment.