UNPKG

mya-parser-vue-loader

Version:

A parser plugin for mya to parser vue component.

286 lines (237 loc) 8.38 kB
var path = require('path'); var objectAssign = require('object-assign'); var hash = require('hash-sum'); var compiler = require('vue-template-compiler'); var genId = require('./lib/gen-id'); var rewriteStyle = require('./lib/style-rewriter'); var compileTemplate = require('./lib/template-compiler'); var insertCSS = require('./lib/insert-css'); var replaceScopedFlag = require('./lib/replace-scoped-flag'); // 缓存模块内容的 md5 var contentCache = {}; // 缓存 style 标签的个数 var styleNumCache = {}; function generateSourceMap(file, sourceMap, content) { sourceMap.sourcesContent = [content]; const mapping = fis.file.wrap(file.dirname + '/' + file.filename + file.rExt + '.map'); mapping.setContent(JSON.stringify(sourceMap, null, 4)); // file.extras = file.extras || {}; // file.extras.derived = file.extras.derived || []; // file.extras.derived.push(mapping); file.derived.push(mapping); } function generateHMRCode(id, output, file) { // 是否第一次初始化 const inited = !contentCache[id]; if (inited) { createContentCache(id); createStyleNumCache(id, output.styles.length); } const isTemplateUpdated = checkContentUpdate(id, output.template && output.template.content, 'template'); const isScriptUpdated = checkContentUpdate(id, clearComment(output.script && output.script.content).trim(), 'script'); const isStyleUpdated = checkContentUpdate(id, output.styles.map(style => style.content || '').join(''), 'style'); const isStyleNumDeleted = checkStyleNumDeleted(id, output.styles.length); file.extras.hmr = file.extras.hmr = {}; if (!inited && isStyleNumDeleted) { // 强制刷新页面 file.extras.hmr.forceReload = true; } if (inited || isScriptUpdated) { file.extras.hmr.noReload = false; return `if (module.hot) { (function () { var hotAPI = window.vueHotReloadApi; hotAPI.install(require('vue'), false); if (!hotAPI.compatible) return; module.hot.accept('${id}'); if (module.hot.data['${id}']) { hotAPI.reload('${id}', __vue__options__); } else { module.hot.data['${id}'] = true; hotAPI.createRecord('${id}', __vue__options__); } })(); }`; } // script 内容未变化,只 hotAPI.rerender,不 hotAPI.reload file.extras.hmr.noReload = true; return `if (module.hot) { (function () { var hotAPI = window.vueHotReloadApi; hotAPI.install(require('vue'), false); if (!hotAPI.compatible) return; module.hot.accept('${id}'); if (module.hot.data['${id}']) { var options = { render: __vue__options__.render, _withStripped: true, staticRenderFns: __vue__options__.staticRenderFns }; hotAPI.rerender('${id}', options); } else { module.hot.data['${id}'] = true; hotAPI.createRecord('${id}', __vue__options__); } })(); }`; } function createContentCache(id) { contentCache[id] = { template: '', script: '', style: '' } } function checkContentUpdate(id, content='', type) { const contentHash = hash(id + content); if (contentHash === contentCache[id][type]) { return false; } contentCache[id][type] = contentHash; return true; } function clearComment(content='') { // content 可能为 null content = content || ''; return content.replace(/^\s*\/\/$/gm, ''); } function createStyleNumCache(id, num) { styleNumCache[id] = num; } function checkStyleNumDeleted(id, num) { if (styleNumCache[id] <= num) { styleNumCache[id] = num; return false; } styleNumCache[id] = num; return true; } // exports module.exports = function(content, file, conf) { var scriptStr = ''; var output, configs, jsLang; // configs configs = objectAssign({ cssScopedFlag: '__vuec__', extractCSS: true, cssScopedIdPrefix: '_v-', cssScopedHashType: 'sum', cssScopedHashLength: 8, styleNameJoin: '', runtimeOnly: false, hot: false }, conf); // 兼容content为buffer的情况 content = content.toString(); // generate css scope id var id = configs.cssScopedIdPrefix + genId(file, configs); // 兼容旧的cssScopedFlag if (configs.cssScopedFlag) { content = replaceScopedFlag(content, configs.cssScopedFlag, id); } // parse var output = compiler.parseComponent(content.toString(), { pad: true }); // check for scoped style nodes var hasScopedStyle = output.styles.some(function (style) { return style.scoped }); // script if (output.script) { scriptStr = output.script.content; jsLang = output.script.lang || 'js'; } else { scriptStr += 'module.exports = {}'; jsLang = 'js'; } fis.once('parser-babel:getSourceMap', function(sourceMap, subpath) { if (file.subpath === subpath) { generateSourceMap(file, sourceMap, content) } }); // 部分内容以 js 的方式编译一次。如果要支持 cooffe 需要这么配置。 // 只针对预编译语言 // fis.match('*.vue:cooffe', { // parser: fis.plugin('cooffe') // }) scriptStr = fis.compile.partial(scriptStr, file, { ext: jsLang, isJsLike: true }); // 开启 sourceMap 时,在 mya-parser-babel 中会触发 parser-babel:getSourceMap // 未开启 sourceMap 时,需要手动触发事件,防止 EventEmitter memory leak fis.emit('parser-babel:getSourceMap', {}, ''); scriptStr += '\nvar __vue__options__;\n'; scriptStr += 'if(exports && exports.__esModule && exports.default){\n'; scriptStr += ' __vue__options__ = exports.default;\n'; scriptStr += '}else{\n'; scriptStr += ' __vue__options__ = module.exports;\n'; scriptStr += '}\n'; if(output.template){ var templateContent = fis.compile.partial(output.template.content, file, { ext: output.template.lang || 'html', isHtmlLike: true }); // runtimeOnly if(configs.runtimeOnly){ var result = compileTemplate(templateContent); if(result){ scriptStr += '__vue__options__.render =' + result.render + '\n'; scriptStr += '__vue__options__.staticRenderFns =' + result.staticRenderFns + '\n'; } }else{ // template scriptStr += '__vue__options__.template = ' + JSON.stringify(templateContent) + '\n'; } } if(hasScopedStyle){ // template scriptStr += '__vue__options__._scopeId = ' + JSON.stringify(id) + '\n'; } // hot module replacement if (configs.hot) { let id = file.subpath.slice(1); scriptStr += generateHMRCode(id, output, file); } // style output.styles.forEach(function(item, index) { if (!item.content && !configs.hot) { return; } // empty string, or all space line if (/^\s*$/.test(item.content) && !configs.hot) { return; } // css也采用片段编译,更好的支持less、sass等其他语言 var styleContent = fis.compile.partial(item.content, file, { ext: item.lang || 'css', isCssLike: true }); styleContent = rewriteStyle(id, styleContent, item.scoped, {}) // 开启 hmr 时,style以内联方式插入页面 if (!configs.extractCSS || configs.hot) { var styleId = output.styles.length == 1 ? file.id : `${file.id}-${index}`; // configs.hot ? // scriptStr += `\n;(${insertCSS})(${JSON.stringify(styleContent.trim())}, '${styleId}');\n` : // scriptStr += '\n;(' + insertCSS + ')(' + JSON.stringify(styleContent) + ');\n'; scriptStr += `\n;(${insertCSS})(${JSON.stringify(styleContent.trim())}, '${styleId}');\n`; return scriptStr; } var styleFileName, styleFile; if (output['styles'].length == 1) { styleFileName = file.realpathNoExt + configs.styleNameJoin + '.css'; } else { styleFileName = file.realpathNoExt + configs.styleNameJoin + '-' + index + '.css'; } styleFile = fis.file.wrap(styleFileName); styleFile.cache = file.cache; styleFile.isCssLike = true; styleFile.setContent(styleContent); fis.compile.process(styleFile); styleFile.links.forEach(function(derived) { file.addLink(derived); }); file.derived.push(styleFile); file.addRequire(styleFile.getId()); }); return scriptStr; };