UNPKG

babel-plugin-define-variables

Version:

一个类似 webpack.DefinePlugin 的 Babel 插件,用于在编译时定义全局变量和常量

269 lines (242 loc) 8.39 kB
const t = require('@babel/types'); const findUp = require('find-up'); const fs = require('fs'); const path = require('path'); const template = require('@babel/template').default; function formatDate(date, fmt) { let o = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds(), 'q+': Math.floor((date.getMonth() + 3) / 3), S: date.getMilliseconds() }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); Object.keys(o).forEach(k => { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))); } }); return fmt; } function fileExists(path) { try { return !fs.accessSync(path, fs.F_OK); } catch (e) { return false; } } function getConfigPath(filename) { let packagePath = findUp.sync('package.json', { cwd: path.dirname(filename), type: 'file' }); if (packagePath && fileExists(packagePath)) return packagePath; } const NOW = formatDate(new Date(), 'yyyy-MM-dd hh:mm:ss'); const constCached = []; function getConstCache(filename) { const pkgPath = getConfigPath(filename); if (!pkgPath || filename === path.resolve(pkgPath)) return {}; let cwd; let cache = constCached[pkgPath]; if (!cache) { let pkg = require(pkgPath); if (!pkg || !Object.keys(pkg).length) return {}; cwd = path.dirname(pkgPath); cache = { pkg, cwd }; constCached[pkgPath] = cache; cache.now = NOW; } cwd = cache.cwd; cache.filename = '/' + path.relative(cwd, filename).replace(/\\/g, '/'); cache.dirname = '/' + path.relative(cwd, path.dirname(filename)).replace(/\\/g, '/'); return cache; } function arr2Expression(arr, parent) { let temp = ''; let vars = {}; arr.forEach((v, i) => { // eslint-disable-next-line no-use-before-define let expr = var2Expression(v, arr); if (!expr) return; let key = `$${i}`; temp += (temp ? ', ' : '') + key; vars[key] = expr; }); return template(`[${temp}]`)(vars); } function obj2Expression(obj, parent) { let props = Object.keys(obj).map(k => { let v = obj[k]; // eslint-disable-next-line no-use-before-define let expr = var2Expression(v, obj); if (!expr) return; return t.objectProperty(t.identifier(k), expr); }).filter(Boolean); return t.objectExpression(props); } function var2Expression(v, parent) { if (t.isNode(v)) return v; if (v === undefined) return; if (Array.isArray(v)) return arr2Expression(v, parent); switch (typeof v) { case 'string': return t.stringLiteral(v); case 'boolean': return t.booleanLiteral(v); case 'number': return t.numericLiteral(v); case 'object': if (v === null) return t.nullLiteral(); if (v instanceof RegExp) return t.regExpLiteral(v.source, v.flags); if (v instanceof Date) return template('new Date(TIME)')({ TIME: t.numericLiteral(v.getTime()) }); if (v instanceof Function) return template(v.toString())(); return obj2Expression(v, parent); default: return t.identifier('undefined'); } } function expr2str(expr) { if (!expr) return ''; if (typeof expr === 'string') return expr; // if (expr.extra) return expr.extra.raw; switch (expr.type) { case 'JSXExpressionContainer': return expr2str(expr.expression); case 'MemberExpression': case 'JSXMemberExpression': // eslint-disable-next-line no-use-before-define return memberExpr2Str(expr); case 'Identifier': case 'JSXIdentifier': return expr.name; case 'JSXNamespacedName': return `${expr.namespace.name}:${expr.name.name}`; case 'ThisExpression': return 'this'; case 'NumericLiteral': case 'BooleanLiteral': case 'StringLiteral': return expr.value; case 'NullLiteral': return 'null'; case 'RegExpLiteral': return `/${expr.pattern}/${expr.flags}`; case 'SpreadElement': return `...${expr2str(expr.argument)}`; case 'BinaryExpression': return `${expr2str(expr.left)} ${expr.operator} ${expr2str(expr.right)}`; case 'UpdateExpression': case 'UnaryExpression': return `${expr.prefix ? expr.operator : ''}${expr2str(expr.argument)}${!expr.prefix ? expr.operator : ''}`; case 'ConditionalExpression': return `${expr2str(expr.test)} ? ${expr2str(expr.consequent)} : ${expr2str(expr.alternate)}`; case 'CallExpression': return `${expr2str(expr.callee)}(${expr.arguments.map(a => expr2str(a)).join(',')})`; case 'NewExpression': return `new ${expr2str(expr.callee)}(${expr.arguments.map(a => expr2str(a)).join(',')})`; case 'VariableDeclarator': return `${expr.id}${expr.init ? ` = ${expr2str(expr.init)}` : ''}`; case 'VariableDeclaration': return `${expr.kind} ${expr.declarations.map(d => expr2str(d))};`; case 'BlockStatement': return `{${expr2str(expr.body)}}`; case 'TemplateLiteral': // eslint-disable-next-line no-use-before-define return temp2var(expr); case 'TaggedTemplateExpression': return `${expr2str(expr2str(expr.tag))}${expr2str(expr.quasi)}`; case 'FunctionExpression': return `function ${expr2str(expr.id)}(${expr.params.map(a => expr2str(a)).join(',')})${expr2str(expr.body)}`; case 'AssignmentPattern': return `${expr2str(expr.left)} = ${expr2str(expr.right)}`; case 'ArrayExpression': case 'ArrayPattern': return `[${expr.elements.map(v => expr2str(v)).join(', ')}]`; case 'ObjectProperty': return `${expr.computed ? `[${expr2str(expr.key)}]` : expr2str(expr.key)}: ${expr2str(expr.value)}`; case 'ObjectMethod': // eslint-disable-next-line return `${expr.kind !== 'method' ? `${expr.kind} ` : ''}${expr2str(expr.key)}(${expr.params.map(a => expr2str(a)).join(', ')})${expr2str(expr.body)}`; case 'ObjectPattern': case 'ObjectExpression': return `{${expr.properties.map(v => expr2str(v)).join(', ')}}`; default: return ''; } } function temp2var(expr) { let arr = [...expr.expressions, ...expr.quasis].sort((a, b) => a.start - b.start); let ret = ''; arr.forEach(v => { if (v.type === 'TemplateElement') ret += v.value.raw; else ret += '${' + expr2str(v) + '}'; }); return '`' + ret + '`'; } function memberExpr2Str(expr) { let objStr; const object = expr.object; if (!object) return String(expr.value); switch (expr.object.type) { case 'MemberExpression': case 'JSXMemberExpression': objStr = memberExpr2Str(expr.object); break; default: objStr = expr2str(expr.object); } let propIsMember = expr.property.type === 'MemberExpression'; let propStr = expr2str(expr.property); return objStr + (objStr && !propIsMember ? '.' : '') + (propIsMember ? `[${propStr}]` : propStr); } function getPackage(filename) { let configPath = filename && getConfigPath(filename); if (configPath) return require(configPath); } const _toString = Object.prototype.toString; function isPlainObject(obj) { return _toString.call(obj) === '[object Object]'; } function mergeObject(target) { function _mergeObject(target, source, copiedObjects) { if (!target) return target; if (!isPlainObject(source)) return target; copiedObjects.push({ source, target }); Object.keys(source).forEach(key => { let v = source[key]; if (isPlainObject(v)) { let copied = copiedObjects.find(c => c.target === v); if (copied) target[key] = copied.target; else { let w = target[key]; if (!isPlainObject(w)) w = target[key] = {}; _mergeObject(w, v, copiedObjects); } } else target[key] = v; }); return target; } let ret = target; let copiedObjects = []; for (let i = 1; i < arguments.length; i++) _mergeObject(ret, arguments[i], copiedObjects); return ret; } const _hasOwnProperty = Object.prototype.hasOwnProperty; /** * @param {object} obj * @param {string} key * @returns {boolean} */ function hasOwnProp(obj, key) { return Boolean(obj) && _hasOwnProperty.call(obj, key); } module.exports = { getConstCache, var2Expression, arr2Expression, obj2Expression, expr2str, getConfigPath, getPackage, mergeObject, hasOwnProp };