Skip to content

Commit ba93b87

Browse files
Jasper De Moordevongovett
authored andcommitted
Initial vue support (#1052)
1 parent 09a959d commit ba93b87

19 files changed

Lines changed: 818 additions & 28 deletions

File tree

.eslintignore

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,7 @@
33
# - the export statement
44
# in the same file
55

6-
/test/integration/dynamic/index.js
7-
/test/integration/dynamic-css/index.js
8-
/test/integration/dynamic-esm/index.js
9-
/test/integration/dynamic-hoist/index.js
10-
/test/integration/dynamic-references-raw/index.js
11-
/test/integration/dynamic-references-raw/local.js
12-
/test/integration/hmr-dynamic/index.js
13-
/test/integration/wasm-async/index.js
14-
/test/integration/wasm-dynamic/index.js
15-
/test/integration/rust/index.js
16-
/test/integration/rust-deps/index.js
17-
/test/integration/rust-cargo/src/index.js
6+
/test/integration/**
187

198
# Generated by the build
209
lib

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"ws": "^4.0.0"
6464
},
6565
"devDependencies": {
66+
"@vue/component-compiler-utils": "^1.0.0",
6667
"babel-cli": "^6.26.0",
6768
"babel-plugin-transform-async-super": "^1.0.0",
6869
"babel-register": "^6.26.0",
@@ -91,7 +92,9 @@
9192
"sinon": "^4.2.2",
9293
"sourcemap-validator": "^1.0.6",
9394
"stylus": "^0.54.5",
94-
"typescript": "^2.7.0"
95+
"typescript": "^2.7.0",
96+
"vue": "^2.5.16",
97+
"vue-template-compiler": "^2.5.16"
9598
},
9699
"scripts": {
97100
"test": "cross-env NODE_ENV=test mocha",

src/Bundler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class Bundler extends EventEmitter {
6969
typeof options.watch === 'boolean' ? options.watch : !isProduction;
7070
const target = options.target || 'browser';
7171
return {
72+
production: isProduction,
7273
outDir: Path.resolve(options.outDir || 'dist'),
7374
outFile: options.outFile || '',
7475
publicURL: publicURL,

src/Parser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Parser {
1717
this.registerExtension('ts', './assets/TypeScriptAsset');
1818
this.registerExtension('tsx', './assets/TypeScriptAsset');
1919
this.registerExtension('coffee', './assets/CoffeeScriptAsset');
20+
this.registerExtension('vue', './assets/VueAsset');
2021
this.registerExtension('json', './assets/JSONAsset');
2122
this.registerExtension('json5', './assets/JSONAsset');
2223
this.registerExtension('yaml', './assets/YAMLAsset');
@@ -28,6 +29,7 @@ class Parser {
2829
this.registerExtension('css', './assets/CSSAsset');
2930
this.registerExtension('pcss', './assets/CSSAsset');
3031
this.registerExtension('styl', './assets/StylusAsset');
32+
this.registerExtension('stylus', './assets/StylusAsset');
3133
this.registerExtension('less', './assets/LESSAsset');
3234
this.registerExtension('sass', './assets/SASSAsset');
3335
this.registerExtension('scss', './assets/SASSAsset');

src/assets/CSSAsset.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ class CSSAsset extends Asset {
119119
return [
120120
{
121121
type: 'css',
122-
value: css
122+
value: css,
123+
cssModules: this.cssModules
123124
},
124125
{
125126
type: 'js',

src/assets/VueAsset.js

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
const Asset = require('../Asset');
2+
const localRequire = require('../utils/localRequire');
3+
const md5 = require('../utils/md5');
4+
const {minify} = require('uglify-es');
5+
6+
class VueAsset extends Asset {
7+
constructor(name, pkg, options) {
8+
super(name, pkg, options);
9+
this.type = 'js';
10+
}
11+
12+
async parse(code) {
13+
// Is being used in component-compiler-utils, errors if not installed...
14+
this.vueTemplateCompiler = await localRequire(
15+
'vue-template-compiler',
16+
this.name
17+
);
18+
this.vue = await localRequire('@vue/component-compiler-utils', this.name);
19+
20+
return this.vue.parse({
21+
source: code,
22+
needMap: this.options.sourceMaps,
23+
filename: this.relativeName, // Used for sourcemaps
24+
sourceRoot: '' // Used for sourcemaps. Override so it doesn't use cwd
25+
});
26+
}
27+
28+
async generate() {
29+
let descriptor = this.ast;
30+
let parts = [];
31+
32+
if (descriptor.script) {
33+
parts.push({
34+
type: descriptor.script.lang || 'js',
35+
value: descriptor.script.content,
36+
sourceMap: descriptor.script.map
37+
});
38+
}
39+
40+
if (descriptor.template) {
41+
parts.push({
42+
type: descriptor.template.lang || 'html',
43+
value: descriptor.template.content.trim()
44+
});
45+
}
46+
47+
if (descriptor.styles) {
48+
for (let style of descriptor.styles) {
49+
parts.push({
50+
type: style.lang || 'css',
51+
value: style.content.trim(),
52+
modules: !!style.module
53+
});
54+
}
55+
}
56+
57+
return parts;
58+
}
59+
60+
async postProcess(generated) {
61+
let result = [];
62+
63+
let hasScoped = this.ast.styles.some(s => s.scoped);
64+
let id = md5(this.name).slice(-6);
65+
let scopeId = hasScoped ? `data-v-${id}` : null;
66+
let optsVar = '$' + id;
67+
68+
// Generate JS output.
69+
let js = this.ast.script ? generated[0].value : '';
70+
let supplemental = `
71+
var ${optsVar} = exports.default || module.exports;
72+
if (typeof ${optsVar} === 'function') {
73+
${optsVar} = ${optsVar}.options;
74+
}
75+
`;
76+
77+
supplemental += this.compileTemplate(generated, scopeId, optsVar);
78+
supplemental += this.compileCSSModules(generated, optsVar);
79+
supplemental += this.compileHMR(generated, optsVar);
80+
81+
if (this.options.minify && supplemental) {
82+
let {code, error} = minify(supplemental, {toplevel: true});
83+
if (error) {
84+
throw error;
85+
}
86+
87+
supplemental = code;
88+
}
89+
90+
js += supplemental;
91+
92+
if (js) {
93+
result.push({
94+
type: 'js',
95+
value: js
96+
});
97+
}
98+
99+
let map = generated.find(r => r.type === 'map');
100+
if (map) {
101+
result.push(map);
102+
}
103+
104+
let css = this.compileStyle(generated, scopeId);
105+
if (css) {
106+
result.push({
107+
type: 'css',
108+
value: css
109+
});
110+
}
111+
112+
return result;
113+
}
114+
115+
compileTemplate(generated, scopeId, optsVar) {
116+
let html = generated.find(r => r.type === 'html');
117+
if (html) {
118+
let isFunctional = this.ast.template.attrs.functional;
119+
let template = this.vue.compileTemplate({
120+
source: html.value,
121+
filename: this.relativeName,
122+
compiler: this.vueTemplateCompiler,
123+
isProduction: this.options.production,
124+
isFunctional,
125+
compilerOptions: {
126+
scopeId
127+
}
128+
});
129+
130+
if (Array.isArray(template.errors) && template.errors.length >= 1) {
131+
throw new Error(template.errors[0]);
132+
}
133+
134+
return `
135+
/* template */
136+
Object.assign(${optsVar}, (function () {
137+
${template.code}
138+
return {
139+
render: render,
140+
staticRenderFns: staticRenderFns,
141+
_compiled: true,
142+
_scopeId: ${JSON.stringify(scopeId)},
143+
functional: ${JSON.stringify(isFunctional)}
144+
};
145+
})());
146+
`;
147+
}
148+
149+
return '';
150+
}
151+
152+
compileCSSModules(generated, optsVar) {
153+
let cssRenditions = generated.filter(r => r.type === 'css');
154+
let cssModulesCode = '';
155+
this.ast.styles.forEach((style, index) => {
156+
if (style.module) {
157+
let cssModules = JSON.stringify(cssRenditions[index].cssModules);
158+
let name = style.module === true ? '$style' : style.module;
159+
cssModulesCode += `\nthis[${JSON.stringify(name)}] = ${cssModules};`;
160+
}
161+
});
162+
163+
if (cssModulesCode) {
164+
cssModulesCode = `function hook(){${cssModulesCode}\n}`;
165+
166+
let isFunctional =
167+
this.ast.template && this.ast.template.attrs.functional;
168+
if (isFunctional) {
169+
return `
170+
/* css modules */
171+
(function () {
172+
${cssModulesCode}
173+
${optsVar}._injectStyles = hook;
174+
var originalRender = ${optsVar}.render;
175+
${optsVar}.render = function (h, context) {
176+
hook.call(context);
177+
return originalRender(h, context);
178+
};
179+
})();
180+
`;
181+
} else {
182+
return `
183+
/* css modules */
184+
(function () {
185+
${cssModulesCode}
186+
${optsVar}.beforeCreate = ${optsVar}.beforeCreate ? ${optsVar}.beforeCreate.concat(hook) : [hook];
187+
})();
188+
`;
189+
}
190+
}
191+
192+
return '';
193+
}
194+
195+
compileStyle(generated, scopeId) {
196+
return generated.filter(r => r.type === 'css').reduce((p, r, i) => {
197+
let css = r.value;
198+
let scoped = this.ast.styles[i].scoped;
199+
200+
// Process scoped styles if needed.
201+
if (scoped) {
202+
let {code, errors} = this.vue.compileStyle({
203+
source: css,
204+
filename: this.relativeName,
205+
id: scopeId,
206+
scoped
207+
});
208+
209+
if (errors.length) {
210+
throw errors[0];
211+
}
212+
213+
css = code;
214+
}
215+
216+
return p + css;
217+
}, '');
218+
}
219+
220+
compileHMR(generated, optsVar) {
221+
if (!this.options.hmr) {
222+
return '';
223+
}
224+
225+
this.addDependency('vue-hot-reload-api');
226+
this.addDependency('vue');
227+
228+
let cssHMR = '';
229+
if (this.ast.styles.length) {
230+
cssHMR = `
231+
var reloadCSS = require('_css_loader');
232+
module.hot.dispose(reloadCSS);
233+
module.hot.accept(reloadCSS);
234+
`;
235+
}
236+
237+
let isFunctional = this.ast.template && this.ast.template.attrs.functional;
238+
239+
return `
240+
/* hot reload */
241+
(function () {
242+
if (module.hot) {
243+
var api = require('vue-hot-reload-api');
244+
api.install(require('vue'));
245+
if (api.compatible) {
246+
module.hot.accept();
247+
if (!module.hot.data) {
248+
api.createRecord('${optsVar}', ${optsVar});
249+
} else {
250+
api.${
251+
isFunctional ? 'rerender' : 'reload'
252+
}('${optsVar}', ${optsVar});
253+
}
254+
}
255+
256+
${cssHMR}
257+
}
258+
})();`;
259+
}
260+
}
261+
262+
module.exports = VueAsset;

src/builtins/hmr-runtime.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
var global = (1, eval)('this');
22
var OldModule = module.bundle.Module;
3+
34
function Module(moduleName) {
45
OldModule.call(this, moduleName);
56
this.hot = {
7+
data: module.bundle.hotData,
8+
_acceptCallbacks: [],
9+
_disposeCallbacks: [],
610
accept: function (fn) {
7-
this._acceptCallback = fn || function () {};
11+
this._acceptCallbacks.push(fn || function () {});
812
},
913
dispose: function (fn) {
10-
this._disposeCallback = fn;
14+
this._disposeCallbacks.push(fn);
1115
}
1216
};
17+
18+
module.bundle.hotData = null;
1319
}
1420

1521
module.bundle.Module = Module;
@@ -102,16 +108,25 @@ function hmrAccept(bundle, id) {
102108
}
103109

104110
var cached = bundle.cache[id];
105-
if (cached && cached.hot._disposeCallback) {
106-
cached.hot._disposeCallback();
111+
bundle.hotData = {};
112+
if (cached) {
113+
cached.hot.data = bundle.hotData;
114+
}
115+
116+
if (cached && cached.hot && cached.hot._disposeCallbacks.length) {
117+
cached.hot._disposeCallbacks.forEach(function (cb) {
118+
cb(bundle.hotData);
119+
});
107120
}
108121

109122
delete bundle.cache[id];
110123
bundle(id);
111124

112125
cached = bundle.cache[id];
113-
if (cached && cached.hot && cached.hot._acceptCallback) {
114-
cached.hot._acceptCallback();
126+
if (cached && cached.hot && cached.hot._acceptCallbacks.length) {
127+
cached.hot._acceptCallbacks.forEach(function (cb) {
128+
cb();
129+
});
115130
return true;
116131
}
117132

0 commit comments

Comments
 (0)