const fs = require('fs'); const path = require('path'); const { icons } = require('./svg.json'); // 待替换图标名称,和目标替换图标名称,映射关系 const ICON_MAP = require('./icon-map'); const PLUGIN_NAME = 't-icon-replace-webpack-plugin'; class TIconReplaceWebpackPlugin { constructor(options) { this.options = { name: PLUGIN_NAME, staticPath: 'static', debug: false, rules: [ /** * 替换按需加载的icon组件 */ { test: /tdesign-icons-vue\/(esm|lib)\/components\/.*\.js$/, modify: (src, pathString) => { let newData = src || ''; // 获取需要替换的icon组件 const fileNameObj = pathString.split('/'); const fileNameExt = fileNameObj[fileNameObj.length - 1]; const fileName = fileNameExt.split('.')[0]; const module = ICON_MAP[fileName]; // console.log('newData', newData); if (module) { newData = this.replace(src); } return newData; }, }, /** * 替换全量t-icon组件 */ { test: /tdesign-icons-vue\/(esm|dist)\/svg-sprite\/svg-sprite\.js$/, modify: (src) => { const str = process.env.RUN_TYPE === 'demo' ? '//tencent-tdgv-1255000078.cos.zjywxc.csp.xc01.cloud.sat.tax/icon/svg.js' : /** * 本地开发模式会编译成绝对路径,如/dev/static/svg.js * 做成物料包会编译成./static/svg.js */ `${process.env.NODE_ENV === 'production' ? '.' : process.env.VUE_APP_CDN_PATH}/${ this.options.staticPath }/svg.js`; // console.log('str', str); return src.replace(/(CDN_ICONFONT_URL = ['|"]).*(['|"])/, `$1${str}$2`); }, }, ], ...options, }; // 如果不是用于demo站点,则把svg.js拷贝到工程目录去 if ( process.env.RUN_TYPE !== 'demo' && // 确保跑在vue-cli下 process.env.VUE_APP_ENV !== undefined && // 非lint模式 /vue-cli-service lint/.test(process.env.npm_lifecycle_script) !== true ) { this.copyJs(); } // return this.replacePlugin(); } apply(compiler) { compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { const modifiedModules = []; const { rules, debug } = this.options; const tapCallback = (params, normalModule) => { const userRequest = normalModule.userRequest || ''; const startIndex = userRequest.lastIndexOf('!') === -1 ? 0 : userRequest.lastIndexOf('!') + 1; const moduleRequest = userRequest.substr(startIndex).replace(/\\/g, '/'); if (modifiedModules.includes(moduleRequest)) { return; } rules.forEach((options, ruleIndex) => { const { test, modify } = options; const isMatched = (() => { if (typeof test === 'function' && test(normalModule)) { return true; } return test instanceof RegExp && test.test(moduleRequest); })(); if (debug && isMatched) { // eslint-disable-next-line no-console console.log(`[${PLUGIN_NAME}] Add loader for module ${moduleRequest} at index ${ruleIndex}.`); } if (isMatched) { normalModule.loaders.push({ loader: require.resolve('./loader.js'), options: { path: moduleRequest, ruleIndex, modify, }, }); modifiedModules.push(moduleRequest); } }); }; compilation.hooks.normalModuleLoader.tap(PLUGIN_NAME, tapCallback); }); } /** * 替换单个icon js文件的核心处理函数 * @param source * @returns {*} */ replace(source = '') { let newSource = source; // let iconName = source.match(/-icon-([\w|-]+)/g)[0]; const iconNameMatch = /id:[ ]?'([\w|-]+)'/g.exec(source); if (iconNameMatch && iconNameMatch.length > 1) { const iconName = iconNameMatch[1]; const newIconName = ICON_MAP[iconName]; const newIconSvg = icons.find((t) => t.name === newIconName); // 存在图标替换 if (newIconName && newIconSvg) { // 匹配 svg / path 属性 进行替换 let attrs = newIconSvg.svgCode.match(/([\w-]+?=".*?")/g); if (!attrs) { console.warn('icon svgCode is not correct, it should like ``'); return; } attrs = attrs.map((t) => { const r = t.split('='); return { key: r[0], value: r[1] }; }); // 不包含 value 的 svg 属性 const pureAttrs = attrs.map((t) => t.key); const regExp = new RegExp(`"*(${pureAttrs.join('|')})"*: ".*?"`, 'g'); newSource = source.replace(regExp, (str) => { for (let i = 0, len = attrs.length; i < len; i++) { const { key, value } = attrs[i]; if (str.indexOf(key) !== -1) { return `"${key}": ${value}`; } } return str; }); } } return newSource; } /** * 拷贝全量svg到项目静态目录 */ copyJs() { fs.copyFileSync( path.resolve(__dirname, 'svg.js'), path.resolve(process.cwd(), `public/${this.options.staticPath}/svg.js`), ); console.log('====>> 拷贝全量svg到项目静态目录成功'); } } module.exports = TIconReplaceWebpackPlugin;