Skip to content

Commit

Permalink
Initial vue support (#1052)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jasper De Moor authored and devongovett committed Mar 28, 2018
1 parent 5ea87e5 commit af25976
Show file tree
Hide file tree
Showing 19 changed files with 818 additions and 28 deletions.
13 changes: 1 addition & 12 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,7 @@
# - the export statement
# in the same file

/test/integration/dynamic/index.js
/test/integration/dynamic-css/index.js
/test/integration/dynamic-esm/index.js
/test/integration/dynamic-hoist/index.js
/test/integration/dynamic-references-raw/index.js
/test/integration/dynamic-references-raw/local.js
/test/integration/hmr-dynamic/index.js
/test/integration/wasm-async/index.js
/test/integration/wasm-dynamic/index.js
/test/integration/rust/index.js
/test/integration/rust-deps/index.js
/test/integration/rust-cargo/src/index.js
/test/integration/**

# Generated by the build
lib
Expand Down
5 changes: 4 additions & 1 deletion packages/core/parcel-bundler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"ws": "^4.0.0"
},
"devDependencies": {
"@vue/component-compiler-utils": "^1.0.0",
"babel-cli": "^6.26.0",
"babel-plugin-transform-async-super": "^1.0.0",
"babel-register": "^6.26.0",
Expand Down Expand Up @@ -91,7 +92,9 @@
"sinon": "^4.2.2",
"sourcemap-validator": "^1.0.6",
"stylus": "^0.54.5",
"typescript": "^2.7.0"
"typescript": "^2.7.0",
"vue": "^2.5.16",
"vue-template-compiler": "^2.5.16"
},
"scripts": {
"test": "cross-env NODE_ENV=test mocha",
Expand Down
1 change: 1 addition & 0 deletions packages/core/parcel-bundler/src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class Bundler extends EventEmitter {
typeof options.watch === 'boolean' ? options.watch : !isProduction;
const target = options.target || 'browser';
return {
production: isProduction,
outDir: Path.resolve(options.outDir || 'dist'),
outFile: options.outFile || '',
publicURL: publicURL,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/parcel-bundler/src/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Parser {
this.registerExtension('ts', './assets/TypeScriptAsset');
this.registerExtension('tsx', './assets/TypeScriptAsset');
this.registerExtension('coffee', './assets/CoffeeScriptAsset');
this.registerExtension('vue', './assets/VueAsset');
this.registerExtension('json', './assets/JSONAsset');
this.registerExtension('json5', './assets/JSONAsset');
this.registerExtension('yaml', './assets/YAMLAsset');
Expand All @@ -28,6 +29,7 @@ class Parser {
this.registerExtension('css', './assets/CSSAsset');
this.registerExtension('pcss', './assets/CSSAsset');
this.registerExtension('styl', './assets/StylusAsset');
this.registerExtension('stylus', './assets/StylusAsset');
this.registerExtension('less', './assets/LESSAsset');
this.registerExtension('sass', './assets/SASSAsset');
this.registerExtension('scss', './assets/SASSAsset');
Expand Down
3 changes: 2 additions & 1 deletion packages/core/parcel-bundler/src/assets/CSSAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class CSSAsset extends Asset {
return [
{
type: 'css',
value: css
value: css,
cssModules: this.cssModules
},
{
type: 'js',
Expand Down
262 changes: 262 additions & 0 deletions packages/core/parcel-bundler/src/assets/VueAsset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
const Asset = require('../Asset');
const localRequire = require('../utils/localRequire');
const md5 = require('../utils/md5');
const {minify} = require('uglify-es');

class VueAsset extends Asset {
constructor(name, pkg, options) {
super(name, pkg, options);
this.type = 'js';
}

async parse(code) {
// Is being used in component-compiler-utils, errors if not installed...
this.vueTemplateCompiler = await localRequire(
'vue-template-compiler',
this.name
);
this.vue = await localRequire('@vue/component-compiler-utils', this.name);

return this.vue.parse({
source: code,
needMap: this.options.sourceMaps,
filename: this.relativeName, // Used for sourcemaps
sourceRoot: '' // Used for sourcemaps. Override so it doesn't use cwd
});
}

async generate() {
let descriptor = this.ast;
let parts = [];

if (descriptor.script) {
parts.push({
type: descriptor.script.lang || 'js',
value: descriptor.script.content,
sourceMap: descriptor.script.map
});
}

if (descriptor.template) {
parts.push({
type: descriptor.template.lang || 'html',
value: descriptor.template.content.trim()
});
}

if (descriptor.styles) {
for (let style of descriptor.styles) {
parts.push({
type: style.lang || 'css',
value: style.content.trim(),
modules: !!style.module
});
}
}

return parts;
}

async postProcess(generated) {
let result = [];

let hasScoped = this.ast.styles.some(s => s.scoped);
let id = md5(this.name).slice(-6);
let scopeId = hasScoped ? `data-v-${id}` : null;
let optsVar = '$' + id;

// Generate JS output.
let js = this.ast.script ? generated[0].value : '';
let supplemental = `
var ${optsVar} = exports.default || module.exports;
if (typeof ${optsVar} === 'function') {
${optsVar} = ${optsVar}.options;
}
`;

supplemental += this.compileTemplate(generated, scopeId, optsVar);
supplemental += this.compileCSSModules(generated, optsVar);
supplemental += this.compileHMR(generated, optsVar);

if (this.options.minify && supplemental) {
let {code, error} = minify(supplemental, {toplevel: true});
if (error) {
throw error;
}

supplemental = code;
}

js += supplemental;

if (js) {
result.push({
type: 'js',
value: js
});
}

let map = generated.find(r => r.type === 'map');
if (map) {
result.push(map);
}

let css = this.compileStyle(generated, scopeId);
if (css) {
result.push({
type: 'css',
value: css
});
}

return result;
}

compileTemplate(generated, scopeId, optsVar) {
let html = generated.find(r => r.type === 'html');
if (html) {
let isFunctional = this.ast.template.attrs.functional;
let template = this.vue.compileTemplate({
source: html.value,
filename: this.relativeName,
compiler: this.vueTemplateCompiler,
isProduction: this.options.production,
isFunctional,
compilerOptions: {
scopeId
}
});

if (Array.isArray(template.errors) && template.errors.length >= 1) {
throw new Error(template.errors[0]);
}

return `
/* template */
Object.assign(${optsVar}, (function () {
${template.code}
return {
render: render,
staticRenderFns: staticRenderFns,
_compiled: true,
_scopeId: ${JSON.stringify(scopeId)},
functional: ${JSON.stringify(isFunctional)}
};
})());
`;
}

return '';
}

compileCSSModules(generated, optsVar) {
let cssRenditions = generated.filter(r => r.type === 'css');
let cssModulesCode = '';
this.ast.styles.forEach((style, index) => {
if (style.module) {
let cssModules = JSON.stringify(cssRenditions[index].cssModules);
let name = style.module === true ? '$style' : style.module;
cssModulesCode += `\nthis[${JSON.stringify(name)}] = ${cssModules};`;
}
});

if (cssModulesCode) {
cssModulesCode = `function hook(){${cssModulesCode}\n}`;

let isFunctional =
this.ast.template && this.ast.template.attrs.functional;
if (isFunctional) {
return `
/* css modules */
(function () {
${cssModulesCode}
${optsVar}._injectStyles = hook;
var originalRender = ${optsVar}.render;
${optsVar}.render = function (h, context) {
hook.call(context);
return originalRender(h, context);
};
})();
`;
} else {
return `
/* css modules */
(function () {
${cssModulesCode}
${optsVar}.beforeCreate = ${optsVar}.beforeCreate ? ${optsVar}.beforeCreate.concat(hook) : [hook];
})();
`;
}
}

return '';
}

compileStyle(generated, scopeId) {
return generated.filter(r => r.type === 'css').reduce((p, r, i) => {
let css = r.value;
let scoped = this.ast.styles[i].scoped;

// Process scoped styles if needed.
if (scoped) {
let {code, errors} = this.vue.compileStyle({
source: css,
filename: this.relativeName,
id: scopeId,
scoped
});

if (errors.length) {
throw errors[0];
}

css = code;
}

return p + css;
}, '');
}

compileHMR(generated, optsVar) {
if (!this.options.hmr) {
return '';
}

this.addDependency('vue-hot-reload-api');
this.addDependency('vue');

let cssHMR = '';
if (this.ast.styles.length) {
cssHMR = `
var reloadCSS = require('_css_loader');
module.hot.dispose(reloadCSS);
module.hot.accept(reloadCSS);
`;
}

let isFunctional = this.ast.template && this.ast.template.attrs.functional;

return `
/* hot reload */
(function () {
if (module.hot) {
var api = require('vue-hot-reload-api');
api.install(require('vue'));
if (api.compatible) {
module.hot.accept();
if (!module.hot.data) {
api.createRecord('${optsVar}', ${optsVar});
} else {
api.${
isFunctional ? 'rerender' : 'reload'
}('${optsVar}', ${optsVar});
}
}
${cssHMR}
}
})();`;
}
}

module.exports = VueAsset;
27 changes: 21 additions & 6 deletions packages/core/parcel-bundler/src/builtins/hmr-runtime.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
var global = (1, eval)('this');
var OldModule = module.bundle.Module;

function Module(moduleName) {
OldModule.call(this, moduleName);
this.hot = {
data: module.bundle.hotData,
_acceptCallbacks: [],
_disposeCallbacks: [],
accept: function (fn) {
this._acceptCallback = fn || function () {};
this._acceptCallbacks.push(fn || function () {});
},
dispose: function (fn) {
this._disposeCallback = fn;
this._disposeCallbacks.push(fn);
}
};

module.bundle.hotData = null;
}

module.bundle.Module = Module;
Expand Down Expand Up @@ -102,16 +108,25 @@ function hmrAccept(bundle, id) {
}

var cached = bundle.cache[id];
if (cached && cached.hot._disposeCallback) {
cached.hot._disposeCallback();
bundle.hotData = {};
if (cached) {
cached.hot.data = bundle.hotData;
}

if (cached && cached.hot && cached.hot._disposeCallbacks.length) {
cached.hot._disposeCallbacks.forEach(function (cb) {
cb(bundle.hotData);
});
}

delete bundle.cache[id];
bundle(id);

cached = bundle.cache[id];
if (cached && cached.hot && cached.hot._acceptCallback) {
cached.hot._acceptCallback();
if (cached && cached.hot && cached.hot._acceptCallbacks.length) {
cached.hot._acceptCallbacks.forEach(function (cb) {
cb();
});
return true;
}

Expand Down
Loading

0 comments on commit af25976

Please sign in to comment.