Skip to content

Commit 082b83b

Browse files
committed
feat(plugin-conventions): always wrap others resources in defer
Wrap other resources (not .html/.js/.ts) in Registration.defer, this will help CSSModule usage in html template.
1 parent 9b8e7f1 commit 082b83b

File tree

8 files changed

+202
-35
lines changed

8 files changed

+202
-35
lines changed

packages/__tests__/plugin-conventions/preprocess-html-template.spec.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ export function getHTMLOnlyElement() {
2727
import * as h0 from "./hello-world.html";
2828
const d0 = h0.getHTMLOnlyElement();
2929
import * as d1 from "foo";
30-
import "./foo-bar.scss";
30+
import { Registration } from '@aurelia/kernel';
31+
import d2 from "./foo-bar.scss";
3132
export const name = "foo-bar";
3233
export const template = "<template></template>";
3334
export default template;
34-
export const dependencies = [ d0, d1 ];
35+
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
3536
let _e;
3637
export function getHTMLOnlyElement() {
3738
if (!_e) {
@@ -44,6 +45,30 @@ export function getHTMLOnlyElement() {
4445
assert.equal(result.code, expected);
4546
});
4647

48+
it('processes template with dependencies, wrap css module id', function () {
49+
const html = '<import from="./hello-world.html" /><template><import from="foo"><require from="./foo-bar.scss"></require></template>';
50+
const expected = `import { CustomElement } from '@aurelia/runtime';
51+
import * as h0 from "./hello-world.html";
52+
const d0 = h0.getHTMLOnlyElement();
53+
import * as d1 from "foo";
54+
import { Registration } from '@aurelia/kernel';
55+
import d2 from "./foo-bar.scss";
56+
export const name = "foo-bar";
57+
export const template = "<template></template>";
58+
export default template;
59+
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
60+
let _e;
61+
export function getHTMLOnlyElement() {
62+
if (!_e) {
63+
_e = CustomElement.define({ name, template, dependencies });
64+
}
65+
return _e;
66+
}
67+
`;
68+
const result = preprocessHtmlTemplate('lo\\FooBar.html', html, undefined, id => `raw-loader!${id}`);
69+
assert.equal(result.code, expected);
70+
});
71+
4772
it('processes template with css dependencies in shadowDOM mode', function () {
4873
const html = '<import from="./hello-world.html" /><template><import from="foo"><require from="./foo-bar.scss"></require></template>';
4974
const expected = `import { CustomElement } from '@aurelia/runtime';
@@ -151,11 +176,12 @@ console.warn("WARN: ShadowDOM is disabled for lo\\\\foo.html. ShadowDOM requires
151176
import * as h0 from "./hello-world.html";
152177
const d0 = h0.getHTMLOnlyElement();
153178
import * as d1 from "foo";
154-
import "./foo-bar.scss";
179+
import { Registration } from '@aurelia/kernel';
180+
import d2 from "./foo-bar.scss";
155181
export const name = "foo";
156182
export const template = "<template></template>";
157183
export default template;
158-
export const dependencies = [ d0, d1 ];
184+
export const dependencies = [ d0, d1, Registration.defer('.css', d2) ];
159185
let _e;
160186
export function getHTMLOnlyElement() {
161187
if (!_e) {

packages/__tests__/plugin-gulp/index.spec.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function preprocess(
2222
describe('plugin-gulp', function () {
2323
it('complains about stream mode', function (done) {
2424
const files: Vinyl[] = [];
25-
const t = plugin.call(undefined, null, preprocess);
25+
const t = plugin.call(undefined, null, false, preprocess);
2626
t.pipe(new Writable({
2727
objectMode: true,
2828
write(file: Vinyl, enc, cb) {
@@ -45,7 +45,7 @@ describe('plugin-gulp', function () {
4545
it('ignores non js/ts/html file', function (done) {
4646
const css = '.a { color: red; }';
4747
const files: Vinyl[] = [];
48-
const t = plugin.call(undefined, null, preprocess);
48+
const t = plugin.call(undefined, null, false, preprocess);
4949
t.pipe(new Writable({
5050
objectMode: true,
5151
write(file: Vinyl, enc, cb) {
@@ -73,7 +73,7 @@ describe('plugin-gulp', function () {
7373
const expected = 'processed src/foo-bar.html content';
7474

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

103103
const files: Vinyl[] = [];
104-
const t = plugin.call(undefined, { mode: 'open' }, preprocess);
104+
const t = plugin.call(undefined, { mode: 'open' }, false, preprocess);
105+
t.pipe(new Writable({
106+
objectMode: true,
107+
write(file: Vinyl, enc, cb) {
108+
files.push(file);
109+
cb();
110+
}
111+
}));
112+
t.on('error', done);
113+
t.on('end', () => {
114+
assert.equal(files.length, 1);
115+
assert.equal(files[0].relative, 'src/foo-bar.html.js');
116+
assert.equal(files[0].contents.toString(), expected);
117+
assert.equal(files[0].sourceMap.version, 3);
118+
done();
119+
});
120+
121+
t.end(new Vinyl({
122+
path: 'src/foo-bar.html',
123+
contents: Buffer.from(content),
124+
sourceMap: {}
125+
}));
126+
});
127+
128+
it('transforms html file in CSSModule mode', function(done) {
129+
const content = 'content';
130+
const expected = 'processed src/foo-bar.html content';
131+
132+
const files: Vinyl[] = [];
133+
const t = plugin.call(undefined, null, true, preprocess);
134+
t.pipe(new Writable({
135+
objectMode: true,
136+
write(file: Vinyl, enc, cb) {
137+
files.push(file);
138+
cb();
139+
}
140+
}));
141+
t.on('error', done);
142+
t.on('end', () => {
143+
assert.equal(files.length, 1);
144+
assert.equal(files[0].relative, 'src/foo-bar.html.js');
145+
assert.equal(files[0].contents.toString(), expected);
146+
assert.equal(files[0].sourceMap.version, 3);
147+
done();
148+
});
149+
150+
t.end(new Vinyl({
151+
path: 'src/foo-bar.html',
152+
contents: Buffer.from(content),
153+
sourceMap: {}
154+
}));
155+
});
156+
157+
it('transforms html file in shadowDOM mode + CSSModule mode', function(done) {
158+
const content = 'content';
159+
const expected = 'processed {"mode":"open"} src/foo-bar.html content';
160+
161+
const files: Vinyl[] = [];
162+
const t = plugin.call(undefined, { mode: 'open' }, true, preprocess);
105163
t.pipe(new Writable({
106164
objectMode: true,
107165
write(file: Vinyl, enc, cb) {
@@ -130,7 +188,7 @@ describe('plugin-gulp', function () {
130188
const expected = 'processed src/foo-bar.js content';
131189

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

160218
const files: Vinyl[] = [];
161-
const t = plugin.call(undefined, null, preprocess);
219+
const t = plugin.call(undefined, null, false, preprocess);
162220
t.pipe(new Writable({
163221
objectMode: true,
164222
write(file: Vinyl, enc, cb) {

packages/__tests__/webpack-loader/loader.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,47 @@ describe('webpack-loader', function () {
5757
loader.call(context, content, preprocess);
5858
});
5959

60+
it('transforms html file in CSSModule mode', function(done) {
61+
const content = 'content';
62+
const expected = 'processed src/foo-bar.html content';
63+
64+
const context = {
65+
async: () => function(err, code, map) {
66+
if (err) {
67+
done(err);
68+
return;
69+
}
70+
assert.equal(code, expected);
71+
assert.equal(map.version, 3);
72+
done();
73+
},
74+
query: { useCSSModule: true },
75+
resourcePath: 'src/foo-bar.html'
76+
};
77+
78+
loader.call(context, content, preprocess);
79+
});
80+
it('transforms html file in shadowDOM mode + CSSModule mode', function(done) {
81+
const content = 'content';
82+
const expected = 'processed {"mode":"open"} src/foo-bar.html content';
83+
84+
const context = {
85+
async: () => function(err, code, map) {
86+
if (err) {
87+
done(err);
88+
return;
89+
}
90+
assert.equal(code, expected);
91+
assert.equal(map.version, 3);
92+
done();
93+
},
94+
query: { defaultShadowOptions: { mode: 'open' }, useCSSModule: true },
95+
resourcePath: 'src/foo-bar.html'
96+
};
97+
98+
loader.call(context, content, preprocess);
99+
});
100+
60101
it('transforms js file', function(done) {
61102
const content = 'content';
62103
const expected = 'processed src/foo-bar.js content';

packages/plugin-conventions/src/preprocess-html-template.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,19 @@ export function preprocessHtmlTemplate(filePath: string, rawHtml: string, defaul
3434

3535
deps.forEach((d, i) => {
3636
const ext = path.extname(d);
37-
38-
if (isCss(ext)) {
39-
if (shadowMode) {
40-
if (!registrationImported) {
41-
statements.push(`import { Registration } from '@aurelia/kernel';\n`);
42-
registrationImported = true;
43-
}
44-
const stringModuleId = stringModuleWrap ? stringModuleWrap(d) : d;
45-
statements.push(`import d${i} from ${s(stringModuleId)};\n`);
46-
viewDeps.push(`Registration.defer('.css', d${i})`);
47-
} else {
48-
statements.push(`import ${s(d)};\n`);
49-
}
50-
} else if (ext === '.html') {
37+
if (ext === '.html') {
5138
statements.push(`import * as h${i} from ${s(d)};\nconst d${i} = h${i}.getHTMLOnlyElement();\n`);
5239
viewDeps.push(`d${i}`);
40+
} else if (ext && ext !== '.js' && ext !== '.ts') {
41+
// Wrap all other unknown resources (including .css, .scss) in defer.
42+
if (!registrationImported) {
43+
statements.push(`import { Registration } from '@aurelia/kernel';\n`);
44+
registrationImported = true;
45+
}
46+
const isCssResource = isCss(ext);
47+
const stringModuleId = isCssResource && shadowMode && stringModuleWrap ? stringModuleWrap(d) : d;
48+
statements.push(`import d${i} from ${s(stringModuleId)};\n`);
49+
viewDeps.push(`Registration.defer('${isCssResource ? '.css' : ext}', d${i})`);
5350
} else {
5451
statements.push(`import * as d${i} from ${s(d)};\n`);
5552
viewDeps.push(`d${i}`);

packages/plugin-gulp/README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ gulp.src('src/**/*.js')
3333
.pipe(babel()); // demo js file with babel here
3434

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

41-
// For apps don't want to use ShadowDOM
43+
// For apps don't want to use ShadowDOM or CSSModule
4244
gulp.src('src/**/*.html')
4345
.pipe(au2());
4446
```
@@ -56,3 +58,7 @@ declare module '*.html' {
5658
export function getHTMLOnlyElement();
5759
}
5860
```
61+
62+
Note: for CSSModule, there are more configuration to be done in webpack config and app main entry.
63+
64+
TODO: add more info for using CSSModule

packages/plugin-gulp/src/index.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ import { preprocess } from '@aurelia/plugin-conventions';
44

55
export default function(options: any = {}) {
66
let shadowOptions;
7+
let useCSSModule = false;
78
if (options && options.defaultShadowOptions) {
89
shadowOptions = options.defaultShadowOptions as { mode: 'open' | 'closed' };
910
}
10-
return plugin(shadowOptions);
11+
if (options && options.useCSSModule) {
12+
useCSSModule = options.useCSSModule;
13+
}
14+
return plugin(shadowOptions, useCSSModule);
1115
}
1216

1317
export function plugin(
14-
shadowOptions?: { mode: 'open' | 'closed' } | null,
18+
shadowOptions?: { mode: 'open' | 'closed' },
19+
useCSSModule?: boolean,
1520
_preprocess = preprocess // for testing
1621
) {
1722
return new Transform({
@@ -23,7 +28,14 @@ export function plugin(
2328
const { extname } = file;
2429
if (extname === '.html' || extname === '.js' || extname === '.ts') {
2530
// Rewrite foo.html to foo.html.js
26-
const result = _preprocess(file.relative, file.contents.toString(), file.base, shadowOptions, stringModuleWrap);
31+
// Don't wrap css module id when using CSSModule
32+
const result = _preprocess(
33+
file.relative,
34+
file.contents.toString(),
35+
file.base,
36+
shadowOptions,
37+
useCSSModule ? undefined : stringModuleWrap
38+
);
2739
if (extname === '.html') {
2840
file.basename += '.js';
2941
}

packages/webpack-loader/README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,23 @@ module: {
3131
{ test: /\.js$/i, use: ['babel-loader', '@aurelia/webpack-loader'], exclude: /node_modules/ },
3232
// For apps in TypeScript with ts-loader
3333
{ test: /\.ts$/i, use: ['ts-loader', '@aurelia/webpack-loader'], exclude: /node_modules/ },
34-
// For apps don't want to use ShadowDOM
34+
// For apps don't want to use ShadowDOM or CSSModule
3535
{ test: /\.html$/i, use: '@aurelia/webpack-loader', exclude: /node_modules/ }
36-
// For apps want to use ShadowDOM
36+
// For apps want to use ShadowDOM or CSSModule
3737
// available defaultShadowOptions are { mode: 'open' }, or { mode: 'closed' }, or null (default).
38-
{ test: /\.html$/i, use: { loader: '@aurelia/webpack-loader', options: { defaultShadowOptions: { mode: 'open' } } }, exclude: /node_modules/ }
38+
// by default, option useCSSModule is false. https://github.com/css-modules/css-modules
39+
// Normally you would not use ShadowDOM and CSSModule together, but our tooling doesn't prevent you doing that.
40+
{
41+
test: /\.html$/i,
42+
use: {
43+
loader: '@aurelia/webpack-loader',
44+
options: {
45+
defaultShadowOptions: { mode: 'open' },
46+
useCSSModule: false
47+
}
48+
},
49+
exclude: /node_modules/
50+
}
3951
]
4052
}
4153
```
@@ -53,3 +65,8 @@ declare module '*.html' {
5365
export function getHTMLOnlyElement();
5466
}
5567
```
68+
69+
Note: for CSSModule, there are more configuration to be done in webpack config and app main entry.
70+
71+
TODO: add more info for using CSSModule
72+

packages/webpack-loader/src/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,26 @@ export function loader(
2121
const cb = this.async() as webpack.loader.loaderCallback;
2222
const options = getOptions(this);
2323
let shadowOptions;
24+
let useCSSModule = false;
2425
if (options && options.defaultShadowOptions) {
2526
shadowOptions = options.defaultShadowOptions as { mode: 'open' | 'closed' };
2627
}
28+
if (options && options.useCSSModule) {
29+
useCSSModule = options.useCSSModule;
30+
}
2731
const filePath = this.resourcePath;
2832
const ext = path.extname(filePath);
2933

3034
try {
3135
if (ext === '.html' || ext === '.js' || ext === '.ts') {
32-
const result = _preprocess(filePath, contents, '', shadowOptions, stringModuleWrap);
33-
36+
// Don't wrap css module id when using CSSModule
37+
const result = _preprocess(
38+
filePath,
39+
contents,
40+
'',
41+
shadowOptions,
42+
useCSSModule ? undefined : stringModuleWrap
43+
);
3444
// webpack uses source-map 0.6.1 typings for RawSourceMap which
3545
// contains typing error version: string (should be number).
3646
// use result.map as any to bypass the typing issue.

0 commit comments

Comments
 (0)