kef-builder-buffet
Version:
buffet-builder构建工具
380 lines (340 loc) • 12.8 kB
JavaScript
'use strict';
const path = require('path');
const semver = require('semver');
const WrapperPlugin = require('./wraper-webpack-plugin');
const env = process.env;
const CONST = require('../../utils/const');
function normalizeEntry(entry, preparedChunks) {
const preparedName = preparedChunks.filter((module) => {
return typeof module.name !== 'undefined';
}).map((module) => module.name);
return Object.keys(entry).concat(preparedName);
};
const branch = (/([^/]+)\/(\d+\.\d+\.\d+)/i.exec(env.BUILD_GIT_BRANCH) || [, , ''])[2];
const publicPath = env.BUILD_ENV === 'cloud' ? `/${env.BUILD_GIT_GROUP}/${env.BUILD_GIT_PROJECT}/${branch}/` : '/build/';
const getIceReadyCode = () => {
let defaultDebugPath = 'null';
if (CONST.isSinglePageApp) {
// spa /build/index.js
defaultDebugPath = '/index.js';
} else {
// 多页面 remove 没有 /build/pages 目录
defaultDebugPath = '/pages/home/index.js';
}
// __asyncLoaded__ = false
// ready 的方法在 __asyncLoaded__ = true 的情况下立即执行
return `
window.ICE = window.ICE || {};
// https: or http:
var protocol = window.location.protocol;
// 如果是 file:, about:, 等等其它的协议
if (!/^http/.test(protocol)) {
protocol = 'http:';
}
window.ICE.protocol = protocol || '';
// smart-loader
var searchStr = window.location.search.replace(/^\\?/, '');
var searchSplited = searchStr.split('&');
var search = searchSplited.reduce(function(prev, current) {
if (current) {
var tuple = current.split('=');
prev[tuple[0]] = tuple[1];
}
return prev;
}, {});
if (search.debug) {
var originValue = '127.0.0.1'; // can not be override
var originPort = search.debugPort || '3333';
var originPath = search.debugPath || ${JSON.stringify(defaultDebugPath)};
if (window.ICE.debug) {
window.ICE.debug.origin = originValue;
window.ICE.debug.port = originPort;
window.ICE.debug.path = originPath;
// 基础路径, 默认是 '/build/', 用于修改 __webpack_public_path__
window.ICE.debug.base = search.debugBase || null;
} else {
window.ICE.debug = {
origin: originValue,
port: originPort,
path: originPath
};
}
}
`;
};
/**
* 获取加载Scripts的代码
* @param {String} reactVersion reactVersion
* @param {Boolean} notProduction 是否DEV模式
* @return {String} 加载REACT的function定义代码
*/
const getLoadScriptsCode = (loadReact, reactVersion, options) => {
const notProduction = process.env.NODE_ENV !== 'production';
const scripts = [
'https://g.alicdn.com/code/icon/babel-polyfill/6.16.0/polyfill.min.js',
];
if (options.injectBabelPolyfill === false) {
scripts.pop();
}
// 大于16.0.0 用umd地址
const useUMDPath = semver.satisfies(reactVersion, '>=16.0.0');
let reactFileMin = `https://g.alicdn.com/code/npm/??react/${reactVersion}/dist/react-with-addons.min.js,react-dom/${reactVersion}/dist/react-dom.min.js`;
let reactFileFull = `https://g.alicdn.com/code/npm/??react/${reactVersion}/dist/react-with-addons.js,react-dom/${reactVersion}/dist/react-dom.js`;
if (useUMDPath) {
reactFileMin = `https://g.alicdn.com/code/npm/??react/${reactVersion}/umd/react.production.min.js,react-dom/${reactVersion}/umd/react-dom.production.min.js`;
reactFileFull = `https://g.alicdn.com/code/npm/??react/${reactVersion}/umd/react.development.js,react-dom/${reactVersion}/umd/react-dom.development.js`;
}
if (options && options.reactFile) {
reactFileMin = reactFileFull = options.reactFile;
if (options.reactFileMin) {
reactFileMin = options.reactFileMin;
}
if (options.reactFileFull) {
reactFileFull = options.reactFileFull;
}
}
// dev 的情况下不使用 min 文件
// url 中含有 ice-debug 的情况不使用 min 文件 (runtime judge)
const loadReactScript = `
var isUrlDev = window.location.href.match(/ice[_|-]debug/i);
if ((${notProduction} || isUrlDev)) {
scriptList.push(
'${reactFileFull}'
);
} else {
scriptList.push(
'${reactFileMin}'
);
}
`;
// 自动加载 css 的 code
let loadCSSCode = '';
if (options.smartLoader) {
loadCSSCode = `
var didStyleLoaded = document.querySelector('link[data-ice-style-loaded]');
if (typeof iceScript !== 'undefined' && iceScript !== null) {
var scriptSrc = iceScript.src;
var scriptRawSrc = iceScript.getAttribute('src') || '';
var linkHref = scriptSrc.replace(/\\.js$/, '.css').replace(/(\\.js)(?=[?#])/i, '.css');
// 通过 src|href 路径匹配, 防止老项目重复加载
var linkWithSameHref = document.querySelector('link[href$="' + scriptRawSrc.replace(/\\.js$/, '.css').replace(/(\\.js)(?=[?#])/i, '.css') + '"]');
if (!didStyleLoaded && !linkWithSameHref) {
cssList.push(linkHref);
}
}
`;
}
return `
var scriptList = ${JSON.stringify(scripts)};
var cssList = [];
var iceScript = document.getElementById('ice-script') || document.currentScript;
${loadCSSCode}
${loadReact ? loadReactScript : ''}
var loadScripts = (function() {
var timer = null;
var headNode = document.head || document.querySelector('head');
function clearTimer() {
if (timer) {
clearTimeout(timer);
timer = null;
}
}
function loadCSS(url, done) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
link.onload = onload;
link.setAttribute('data-ice-style-loaded', 'true');
var timer = setTimeout(function() {
console.warn('加载 css 超时!');
done && done(new Error('timeout for 5s'));
}, 5 * 1000); // 超时时间 5s
function onload() {
clearTimeout(timer);
done && done(null, link);
}
headNode.appendChild(link);
}
function load(url, done) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
clearTimer();
timer = setTimeout(function() {
clearTimer();
done('请求超时');
}, 60 * 1000);
function onerror(evt) {
script.onerror = null;
script = null;
clearTimer();
done('JS Can not load: ' + url);
}
function onload() {
var readyState = script.readyState;
if (!readyState ||
readyState === 'loaded' ||
readyState === 'complete') {
script.onreadystatechange = script.onload = null;
script = undefined;
clearTimer();
done(null);
}
}
if ('onload' in script) {
script.onload = onload;
script.onerror = onerror;
} else {
script.onreadystatechange = onload;
}
script.onreadystatechange = script.onload = onload;
script.src = url;
headNode.insertBefore(script, headNode.firstChild);
}
return function(scriptList, cssList, callback) {
if (!headNode) {
throw new Error('页面中必须存在 head 元素');
}
// 兼容两个参数, cssList 为可选
if (typeof cssList === 'function') {
callback = cssList;
cssList = [];
}
var loadList = cssList.map(function(url) {
return { type: 'css', url: url };
}).concat(scriptList.map(function(url) {
return { type: 'js', url: url };
}));
var counter = loadList.length;
(function run(counter){
if (counter === 0) {
return callback();
}
var current = loadList.shift();
var loadFn = current.type === 'css' ? loadCSS : load;
if (current.type === 'js' ){
// 已经存在 react 不加载
if (
window.React &&
window.React.version
) {
if (window.React.version !== '${reactVersion}') {
console.warn('当前页面已加载 React 版本 %s,可能会导致功能失效!推荐使用 ${reactVersion}', window.React.version);
}
return run(loadList.length);
} else if ( // 已经存在 babel-polyfill 不加载
window._babelPolyfill &&
current.url.indexOf('babel-polyfill') !== -1
) {
return run(loadList.length);
}
}
loadFn(current.url, function(err) {
if (err) {
console.error(err);
return;
}
run(loadList.length);
});
})(counter);
};
})();
`;
};
/**
* [wrapBefore description]
* @param {Boolean} loadReact 是否使用 wrap-react boolean
* @param {String} reactVersion reactVersion
* @param {Object} abcConfig abc.json
* @return {String} wrap before code
*/
const wrapBefore = (loadReact, reactVersion, options) => {
const iceReadyCode = getIceReadyCode();
const loadScriptsCode = getLoadScriptsCode(loadReact, reactVersion, options) || '';
return `
(function(){
/* ICE READY CODE START */
${iceReadyCode}
/* ICE READY CODE END */
/* ICE LOADSCRIPTS START */
${loadScriptsCode}
/* ICE LOADSCRIPTS END */
${
options.smartLoader ? `
if (iceScript === null) {
console.error('!您正在使用 ICE 智能调试服务, 但是页面上的 script 节点找不到 ice-script 的 id, 请检查!');
} else if (window.ICE.debug && window.ICE.debug.origin) {
var scriptPath = (iceScript.src.replace(/^https?:\\/\\//, '').match(/(\\/.*)/) || ['', '/index.js'])[1];
window.ICE.debug.customJS = window.ICE.protocol + '//' + window.ICE.debug.origin + ':' + window.ICE.debug.port + (window.ICE.debug.path || scriptPath);
cssList = [
window.ICE.debug.customJS
.replace(/\\.js$/i, '.css')
.replace(/(\\.js)(?=[?#])/i, '.css')
];
var linkHref = (iceScript.getAttribute('src') || '').replace(/\\.js$/i, '.css')
.replace(/(\\.js)(?=[?#])/i, '.css');
var currentLink = document.querySelector('link[href$="' + linkHref + '"]');
if (currentLink) {
// debug 下, 移除原先加载的 css
currentLink.parentElement.removeChild(currentLink);
}
}
if (iceScript !== null && window.ICE.debug && window.ICE.debug.customJS && !window.ICE.debug.skip) {
window.ICE.debug.skip = true;
loadScripts([window.ICE.debug.customJS], function() {
console.warn('您正在使用 ICE 智能调试服务, 目前加载的 JS 和 CSS 路径为:' );
console.warn(window.ICE.debug.customJS + "\\n" + cssList[0]);
});
return;
}
` : ''
}
loadScripts(scriptList, cssList, function() {
`;
};
/**
* [wrapAfter description]
* @return {String} wrap after code
*/
const wrapAfter = () => {
return `
/* ICE READY TRIGGER START */
/* ICE READY TRIGGER END */
}); // END loadScripts callback
})(); // END ICE wrap
`;
};
/**
* entry 打包后的代码 wraper 用于自动加载 react 、babel
*
* @param {Object} options abc.options
*/
module.exports = function (options) {
const reactVersion = options.reactVersion;
const loadReact = options.wrapReact === true;
return new WrapperPlugin({
header(filename, compilerEntry, compilationPreparedChunks) {
const entiesChunkName = normalizeEntry(compilerEntry, compilationPreparedChunks);
// 是 js 文件,而且去掉扩展名后在 entry 的 key 里
const loaderFilename = /\.js$/.test(filename) &&
filename.replace(/\.\w+$/, '');
// output文件名保持不变
if (entiesChunkName.length &&
entiesChunkName.indexOf(loaderFilename) !== -1) {
return wrapBefore(loadReact, reactVersion, options);
}
return '';
},
footer(filename, compilerEntry, compilationPreparedChunks) {
const entiesChunkName = normalizeEntry(compilerEntry, compilationPreparedChunks);
const loaderFilename = /\.js$/.test(filename) &&
filename.replace(/\.\w+$/, '');
if (entiesChunkName.length &&
entiesChunkName.indexOf(loaderFilename) !== -1) {
return wrapAfter(loadReact);
}
return '';
}
});
};