diff --git a/.antd-tools.config.js b/.antd-tools.config.js new file mode 100644 index 000000000..7a5bec367 --- /dev/null +++ b/.antd-tools.config.js @@ -0,0 +1,195 @@ +const fs = require('fs'); +const path = require('path'); +const defaultVars = require('./scripts/default-vars'); +const darkVars = require('./scripts/dark-vars'); +const compactVars = require('./scripts/compact-vars'); + +function generateThemeFileContent(theme) { + return `const { ${theme}ThemeSingle } = require('./theme');\nconst defaultTheme = require('./default-theme');\n +module.exports = { + ...defaultTheme, + ...${theme}ThemeSingle +}`; +} + +// We need compile additional content for antd user +function finalizeCompile() { + if (fs.existsSync(path.join(__dirname, './lib'))) { + // Build a entry less file to dist/antd.less + const componentsPath = path.join(process.cwd(), 'components'); + let componentsLessContent = ''; + // Build components in one file: lib/style/components.less + fs.readdir(componentsPath, (err, files) => { + files.forEach(file => { + if (fs.existsSync(path.join(componentsPath, file, 'style', 'index.less'))) { + componentsLessContent += `@import "../${path.posix.join( + file, + 'style', + 'index-pure.less', + )}";\n`; + } + }); + fs.writeFileSync( + path.join(process.cwd(), 'lib', 'style', 'components.less'), + componentsLessContent, + ); + }); + } +} + +function buildThemeFile(theme, vars) { + // Build less entry file: dist/antd.${theme}.less + if (theme !== 'default') { + fs.writeFileSync( + path.join(process.cwd(), 'dist', `antd.${theme}.less`), + `@import "../lib/style/${theme}.less";\n@import "../lib/style/components.less";`, + ); + // eslint-disable-next-line no-console + console.log(`Built a entry less file to dist/antd.${theme}.less`); + } else { + fs.writeFileSync( + path.join(process.cwd(), 'dist', `default-theme.js`), + `module.exports = ${JSON.stringify(vars, null, 2)};\n`, + ); + return; + } + + // Build ${theme}.js: dist/${theme}-theme.js, for less-loader + + fs.writeFileSync( + path.join(process.cwd(), 'dist', `theme.js`), + `const ${theme}ThemeSingle = ${JSON.stringify(vars, null, 2)};\n`, + { + flag: 'a', + }, + ); + + fs.writeFileSync( + path.join(process.cwd(), 'dist', `${theme}-theme.js`), + generateThemeFileContent(theme), + ); + + // eslint-disable-next-line no-console + console.log(`Built a ${theme} theme js file to dist/${theme}-theme.js`); +} + +function finalizeDist() { + if (fs.existsSync(path.join(__dirname, './dist'))) { + // Build less entry file: dist/antd.less + fs.writeFileSync( + path.join(process.cwd(), 'dist', 'antd.less'), + '@import "../lib/style/default.less";\n@import "../lib/style/components.less";', + ); + // eslint-disable-next-line no-console + fs.writeFileSync( + path.join(process.cwd(), 'dist', 'theme.js'), + `const defaultTheme = require('./default-theme.js');\n`, + ); + // eslint-disable-next-line no-console + console.log('Built a entry less file to dist/antd.less'); + buildThemeFile('default', defaultVars); + buildThemeFile('dark', darkVars); + buildThemeFile('compact', compactVars); + buildThemeFile('variable', {}); + fs.writeFileSync( + path.join(process.cwd(), 'dist', `theme.js`), + ` +function getThemeVariables(options = {}) { + let themeVar = { + 'hack': \`true;@import "\${require.resolve('antd/lib/style/color/colorPalette.less')}";\`, + ...defaultTheme + }; + if(options.dark) { + themeVar = { + ...themeVar, + ...darkThemeSingle + } + } + if(options.compact){ + themeVar = { + ...themeVar, + ...compactThemeSingle + } + } + return themeVar; +} + +module.exports = { + darkThemeSingle, + compactThemeSingle, + getThemeVariables +}`, + { + flag: 'a', + }, + ); + } +} + +function isComponentStyleEntry(file) { + return file.path.match(/style(\/|\\)index\.tsx/); +} + +function needTransformStyle(content) { + return content.includes('../../style/index.less') || content.includes('./index.less'); +} + +module.exports = { + compile: { + includeLessFile: [/(\/|\\)components(\/|\\)style(\/|\\)default.less$/], + transformTSFile(file) { + if (isComponentStyleEntry(file)) { + let content = file.contents.toString(); + + if (needTransformStyle(content)) { + const cloneFile = file.clone(); + + // Origin + content = content.replace('../../style/index.less', '../../style/default.less'); + cloneFile.contents = Buffer.from(content); + + return cloneFile; + } + } + }, + transformFile(file) { + if (isComponentStyleEntry(file)) { + const indexLessFilePath = file.path.replace('index.tsx', 'index.less'); + + if (fs.existsSync(indexLessFilePath)) { + // We put origin `index.less` file to `index-pure.less` + const pureFile = file.clone(); + pureFile.contents = Buffer.from(fs.readFileSync(indexLessFilePath, 'utf8')); + pureFile.path = pureFile.path.replace('index.tsx', 'index-pure.less'); + + // Rewrite `index.less` file with `root-entry-name` + const indexLessFile = file.clone(); + indexLessFile.contents = Buffer.from( + [ + // Inject variable + '@root-entry-name: default;', + // Point to origin file + "@import './index-pure.less';", + ].join('\n\n'), + ); + indexLessFile.path = indexLessFile.path.replace('index.tsx', 'index.less'); + + return [indexLessFile, pureFile]; + } + } + + return []; + }, + lessConfig: { + modifyVars: { + 'root-entry-name': 'default', + }, + }, + finalize: finalizeCompile, + }, + dist: { + finalize: finalizeDist, + }, + generateThemeFileContent, + bail: true, +}; diff --git a/.gitignore b/.gitignore index 528ba0b75..a34900353 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,5 @@ vetur/ report.html site/src/router/demoRoutes.js + +components/version/version.tsx diff --git a/antd-tools/apiCollection.js b/antd-tools/apiCollection.js new file mode 100644 index 000000000..99843601c --- /dev/null +++ b/antd-tools/apiCollection.js @@ -0,0 +1,68 @@ +// Read all the api from current documents + +const glob = require('glob'); +const fs = require('fs'); + +const COMPONENT_NAME = /components\/([^/]*)/; +const PROP_NAME = /^\s*\|\s*([^\s|]*)/; + +const components = {}; + +function mappingPropLine(component, line) { + const propMatch = line.match(PROP_NAME); + if (!propMatch) return; + + const propName = propMatch[1]; + if (!/^[a-z]/.test(propName)) return; + + components[component] = Array.from(new Set([...(components[component] || []), propName])); +} + +function apiReport(entities) { + const apis = {}; + Object.keys(entities).forEach(component => { + const apiList = entities[component]; + apiList.forEach(api => { + if (typeof apis[api] === 'function') { + apis[api] = []; + } + apis[api] = [...(apis[api] || []), component]; + }); + }); + + return apis; +} + +function printReport(apis) { + const apiList = Object.keys(apis).map(api => ({ + name: api, + componentList: apis[api], + })); + apiList.sort((a, b) => b.componentList.length - a.componentList.length); + // eslint-disable-next-line no-console + console.log('| name | components | comments |'); + // eslint-disable-next-line no-console + console.log('| ---- | ---------- | -------- |'); + apiList.forEach(({ name, componentList }) => { + // eslint-disable-next-line no-console + console.log('|', name, '|', componentList.join(', '), '| |'); + }); +} + +module.exports = () => { + glob('components/*/*.md', (error, files) => { + files.forEach(filePath => { + // Read md file to parse content + const content = fs.readFileSync(filePath, 'utf8'); + const component = filePath.match(COMPONENT_NAME)[1]; + + // Parse lines to get API + const lines = content.split(/[\r\n]+/); + lines.forEach(line => { + mappingPropLine(component, line); + }); + }); + + printReport(apiReport(components)); + }); +}; diff --git a/antd-tools/cli/run.js b/antd-tools/cli/run.js index b60b54bb0..e1466aa6c 100644 --- a/antd-tools/cli/run.js +++ b/antd-tools/cli/run.js @@ -4,7 +4,6 @@ 'use strict'; require('colorful').colorful(); -require('colorful').isatty = true; const gulp = require('gulp'); const program = require('commander'); diff --git a/antd-tools/getNpm.js b/antd-tools/getNpm.js new file mode 100644 index 000000000..52eee5011 --- /dev/null +++ b/antd-tools/getNpm.js @@ -0,0 +1,17 @@ +'use strict'; + +const runCmd = require('./runCmd'); + +module.exports = function (done) { + if (process.env.NPM_CLI) { + done(process.env.NPM_CLI); + return; + } + runCmd('which', ['tnpm'], code => { + let npm = 'npm'; + if (!code) { + npm = 'tnpm'; + } + done(npm); + }); +}; diff --git a/antd-tools/gulpfile.js b/antd-tools/gulpfile.js index 6a3243cec..9090cfab8 100644 --- a/antd-tools/gulpfile.js +++ b/antd-tools/gulpfile.js @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -const { getProjectPath } = require('./utils/projectHelper'); +const { getProjectPath, getConfig } = require('./utils/projectHelper'); const runCmd = require('./runCmd'); const getBabelCommonConfig = require('./getBabelCommonConfig'); const merge2 = require('merge2'); @@ -26,6 +26,7 @@ const stripCode = require('gulp-strip-code'); const compareVersions = require('compare-versions'); const getTSCommonConfig = require('./getTSCommonConfig'); const replaceLib = require('./replaceLib'); +const sortApiTable = require('./sortApiTable'); const packageJson = require(getProjectPath('package.json')); const tsDefaultReporter = ts.reporter.defaultReporter(); @@ -49,11 +50,17 @@ function dist(done) { } const info = stats.toJson(); + const { dist: { finalize } = {}, bail } = getConfig(); if (stats.hasErrors()) { - console.error(info.errors); + (info.errors || []).forEach(error => { + console.error(error); + }); + // https://github.com/ant-design/ant-design/pull/31662 + if (bail) { + process.exit(1); + } } - if (stats.hasWarnings()) { console.warn(info.warnings); } @@ -68,6 +75,11 @@ function dist(done) { version: false, }); console.log(buildInfo); + // Additional process of dist finalize + if (finalize) { + console.log('[Dist] Finalization...'); + finalize(); + } done(0); }); } @@ -103,7 +115,7 @@ function babelify(js, modules) { if (modules === false) { babelConfig.plugins.push(replaceLib); } - let stream = js.pipe(babel(babelConfig)).pipe( + const stream = js.pipe(babel(babelConfig)).pipe( through2.obj(function z(file, encoding, next) { this.push(file.clone()); if (file.path.match(/\/style\/index\.(js|jsx|ts|tsx)$/)) { @@ -128,33 +140,40 @@ function babelify(js, modules) { next(); }), ); - if (modules === false) { - stream = stream.pipe( - stripCode({ - start_comment: '@remove-on-es-build-begin', - end_comment: '@remove-on-es-build-end', - }), - ); - } return stream.pipe(gulp.dest(modules === false ? esDir : libDir)); } function compile(modules) { + const { compile: { transformTSFile, transformFile, includeLessFile = [] } = {} } = getConfig(); rimraf.sync(modules !== false ? libDir : esDir); + + // =============================== LESS =============================== const less = gulp .src(['components/**/*.less']) .pipe( through2.obj(function (file, encoding, next) { - this.push(file.clone()); + // Replace content + const cloneFile = file.clone(); + const content = file.contents.toString().replace(/^\uFEFF/, ''); + + cloneFile.contents = Buffer.from(content); + + // Clone for css here since `this.push` will modify file.path + const cloneCssFile = cloneFile.clone(); + + this.push(cloneFile); + + // Transform less file if ( - file.path.match(/\/style\/index\.less$/) || - file.path.match(/\/style\/v2-compatible-reset\.less$/) + file.path.match(/(\/|\\)style(\/|\\)index\.less$/) || + file.path.match(/(\/|\\)style(\/|\\)v2-compatible-reset\.less$/) || + includeLessFile.some(regex => file.path.match(regex)) ) { - transformLess(file.path) + transformLess(cloneCssFile.contents.toString(), cloneCssFile.path) .then(css => { - file.contents = Buffer.from(css); - file.path = file.path.replace(/\.less$/, '.css'); - this.push(file); + cloneCssFile.contents = Buffer.from(css); + cloneCssFile.path = cloneCssFile.path.replace(/\.less$/, '.css'); + this.push(cloneCssFile); next(); }) .catch(e => { @@ -170,6 +189,25 @@ function compile(modules) { .src(['components/**/*.@(png|svg)']) .pipe(gulp.dest(modules === false ? esDir : libDir)); let error = 0; + + // =============================== FILE =============================== + let transformFileStream; + + if (transformFile) { + transformFileStream = gulp + .src(['components/**/*.tsx']) + .pipe( + through2.obj(function (file, encoding, next) { + let nextFile = transformFile(file) || file; + nextFile = Array.isArray(nextFile) ? nextFile : [nextFile]; + nextFile.forEach(f => this.push(f)); + next(); + }), + ) + .pipe(gulp.dest(modules === false ? esDir : libDir)); + } + + // ================================ TS ================================ const source = [ 'components/**/*.js', 'components/**/*.jsx', @@ -179,7 +217,29 @@ function compile(modules) { '!components/*/__tests__/*', ]; - const tsResult = gulp.src(source).pipe( + // Strip content if needed + let sourceStream = gulp.src(source); + if (modules === false) { + sourceStream = sourceStream.pipe( + stripCode({ + start_comment: '@remove-on-es-build-begin', + end_comment: '@remove-on-es-build-end', + }), + ); + } + + if (transformTSFile) { + sourceStream = sourceStream.pipe( + through2.obj(function (file, encoding, next) { + let nextFile = transformTSFile(file) || file; + nextFile = Array.isArray(nextFile) ? nextFile : [nextFile]; + nextFile.forEach(f => this.push(f)); + next(); + }), + ); + } + + const tsResult = sourceStream.pipe( ts(tsConfig, { error(e) { tsDefaultReporter.error(e); @@ -199,7 +259,7 @@ function compile(modules) { tsResult.on('end', check); const tsFilesStream = babelify(tsResult.js, modules); const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? esDir : libDir)); - return merge2([less, tsFilesStream, tsd, assets]); + return merge2([less, tsFilesStream, tsd, assets, transformFileStream].filter(s => s)); } function tag() { @@ -420,7 +480,11 @@ gulp.task( const npmArgs = getNpmArgs(); if (npmArgs) { for (let arg = npmArgs.shift(); arg; arg = npmArgs.shift()) { - if (/^pu(b(l(i(sh?)?)?)?)?$/.test(arg) && npmArgs.indexOf('--with-antd-tools') < 0) { + if ( + /^pu(b(l(i(sh?)?)?)?)?$/.test(arg) && + npmArgs.indexOf('--with-antd-tools') < 0 && + !process.env.npm_config_with_antd_tools + ) { reportError(); done(1); return; @@ -430,3 +494,11 @@ gulp.task( done(); }), ); + +gulp.task( + 'sort-api-table', + gulp.series(done => { + sortApiTable(); + done(); + }), +); diff --git a/antd-tools/replaceLib.js b/antd-tools/replaceLib.js index 08d84b4ea..e9da6e86c 100644 --- a/antd-tools/replaceLib.js +++ b/antd-tools/replaceLib.js @@ -12,6 +12,19 @@ function replacePath(path) { path.node.source.value = esModule; } } + + // @ant-design/icons-vue/xxx => @ant-design/icons-vue/es/icons/xxx + const antdIconMatcher = /@ant-design\/icons-vue\/([^/]*)$/; + if (path.node.source && antdIconMatcher.test(path.node.source.value)) { + const esModule = path.node.source.value.replace( + antdIconMatcher, + (_, iconName) => `@ant-design/icons-vue/es/icons/${iconName}`, + ); + const esPath = dirname(getProjectPath('node_modules', esModule)); + if (fs.existsSync(esPath)) { + path.node.source.value = esModule; + } + } } function replaceLib() { diff --git a/antd-tools/runCmd.js b/antd-tools/runCmd.js index f5a822a97..b25ca5711 100644 --- a/antd-tools/runCmd.js +++ b/antd-tools/runCmd.js @@ -1,9 +1,17 @@ 'use strict'; +const isWindows = require('is-windows'); const getRunCmdEnv = require('./utils/getRunCmdEnv'); function runCmd(cmd, _args, fn) { const args = _args || []; + + if (isWindows()) { + args.unshift(cmd); + args.unshift('/c'); + cmd = process.env.ComSpec; + } + const runner = require('child_process').spawn(cmd, args, { // keep color stdio: 'inherit', diff --git a/antd-tools/sortApiTable.js b/antd-tools/sortApiTable.js new file mode 100644 index 000000000..56a2bd21b --- /dev/null +++ b/antd-tools/sortApiTable.js @@ -0,0 +1,165 @@ +const program = require('commander'); +const majo = require('majo'); +const fs = require('fs'); +const path = require('path'); +const chalk = require('chalk'); + +const unified = require('unified'); +const parse = require('remark-parse'); +const stringify = require('remark-stringify'); + +const yamlConfig = require('remark-yaml-config'); +const frontmatter = require('remark-frontmatter'); + +let fileAPIs = {}; +const remarkWithYaml = unified() + .use(parse) + .use(stringify, { + paddedTable: false, + listItemIndent: 1, + stringLength: () => 3, + }) + .use(frontmatter) + .use(yamlConfig); + +const stream = majo.majo(); + +function getCellValue(node) { + return node.children[0].children[0].value; +} + +// from small to large +const sizeBreakPoints = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl']; + +const whiteMethodList = ['afterChange', 'beforeChange']; + +const groups = { + isDynamic: val => /^on[A-Z]/.test(val) || whiteMethodList.indexOf(val) > -1, + isSize: val => sizeBreakPoints.indexOf(val) > -1, +}; + +function asciiSort(prev, next) { + if (prev > next) { + return 1; + } + + if (prev < next) { + return -1; + } + + return 0; +} + +// follow the alphabet order +function alphabetSort(nodes) { + // use toLowerCase to keep `case insensitive` + return nodes.sort((...comparison) => + asciiSort(...comparison.map(val => getCellValue(val).toLowerCase())), + ); +} + +function sizeSort(nodes) { + return nodes.sort((...comparison) => + asciiSort(...comparison.map(val => sizeBreakPoints.indexOf(getCellValue(val).toLowerCase()))), + ); +} + +function sort(ast, filename) { + const nameMatch = filename.match(/^components\/([^/]*)\//); + const componentName = nameMatch[1]; + fileAPIs[componentName] = fileAPIs[componentName] || { + static: new Set(), + size: new Set(), + dynamic: new Set(), + }; + + ast.children.forEach(child => { + const staticProps = []; + // prefix with `on` + const dynamicProps = []; + // one of ['xs', 'sm', 'md', 'lg', 'xl'] + const sizeProps = []; + + // find table markdown type + if (child.type === 'table') { + // slice will create new array, so sort can affect the original array. + // slice(1) cut down the thead + child.children.slice(1).forEach(node => { + const value = getCellValue(node); + if (groups.isDynamic(value)) { + dynamicProps.push(node); + fileAPIs[componentName].dynamic.add(value); + } else if (groups.isSize(value)) { + sizeProps.push(node); + fileAPIs[componentName].size.add(value); + } else { + staticProps.push(node); + fileAPIs[componentName].static.add(value); + } + }); + + // eslint-disable-next-line + child.children = [ + child.children[0], + ...alphabetSort(staticProps), + ...sizeSort(sizeProps), + ...alphabetSort(dynamicProps), + ]; + } + }); + + return ast; +} + +function sortAPI(md, filename) { + return remarkWithYaml.stringify(sort(remarkWithYaml.parse(md), filename)); +} + +function sortMiddleware(ctx) { + Object.keys(ctx.files).forEach(filename => { + const content = ctx.fileContents(filename); + ctx.writeContents(filename, sortAPI(content, filename)); + }); +} + +module.exports = () => { + fileAPIs = {}; + + program + .version('0.1.0') + .option( + '-f, --file [file]', + 'Specify which file to be transformed', + // default value + 'components/**/index.+(zh-CN|en-US).md', + ) + .option('-o, --output [output]', 'Specify component api output path', '~component-api.json') + .parse(process.argv); + // Get the markdown file all need to be transformed + + /* eslint-disable no-console */ + stream + .source(program.file) + .use(sortMiddleware) + .dest('.') + .then(() => { + if (program.output) { + const data = {}; + Object.keys(fileAPIs).forEach(componentName => { + data[componentName] = { + static: [...fileAPIs[componentName].static], + size: [...fileAPIs[componentName].size], + dynamic: [...fileAPIs[componentName].dynamic], + }; + }); + + const reportPath = path.resolve(program.output); + fs.writeFileSync(reportPath, JSON.stringify(data, null, 2), 'utf8'); + console.log(chalk.cyan(`API list file: ${reportPath}`)); + } + }) + .then(() => { + console.log(chalk.green(`sort ant-design-vue api successfully!`)); + }); + /* eslint-enable no-console */ +}; diff --git a/antd-tools/transformLess.js b/antd-tools/transformLess.js index 370ffc17b..0e949b243 100644 --- a/antd-tools/transformLess.js +++ b/antd-tools/transformLess.js @@ -1,16 +1,14 @@ const less = require('less'); -const { readFileSync } = require('fs'); const path = require('path'); const postcss = require('postcss'); -const NpmImportPlugin = require('less-plugin-npm-import'); const autoprefixer = require('autoprefixer'); +const NpmImportPlugin = require('less-plugin-npm-import'); +const { getConfig } = require('./utils/projectHelper'); -function transformLess(lessFile, config = {}) { +function transformLess(lessContent, lessFilePath, config = {}) { const { cwd = process.cwd() } = config; - const resolvedLessFile = path.resolve(cwd, lessFile); - - let data = readFileSync(resolvedLessFile, 'utf-8'); - data = data.replace(/^\uFEFF/, ''); + const { compile: { lessConfig } = {} } = getConfig(); + const resolvedLessFile = path.resolve(cwd, lessFilePath); // Do less compile const lessOpts = { @@ -18,13 +16,12 @@ function transformLess(lessFile, config = {}) { filename: resolvedLessFile, plugins: [new NpmImportPlugin({ prefix: '~' })], javascriptEnabled: true, + ...lessConfig, }; return less - .render(data, lessOpts) + .render(lessContent, lessOpts) .then(result => postcss([autoprefixer]).process(result.css, { from: undefined })) - .then(r => { - return r.css; - }); + .then(r => r.css); } module.exports = transformLess; diff --git a/antd-tools/utils/CleanUpStatsPlugin.js b/antd-tools/utils/CleanUpStatsPlugin.js index 5029bba39..300168c4f 100644 --- a/antd-tools/utils/CleanUpStatsPlugin.js +++ b/antd-tools/utils/CleanUpStatsPlugin.js @@ -24,13 +24,13 @@ class CleanUpStatsPlugin { apply(compiler) { compiler.hooks.done.tap('CleanUpStatsPlugin', stats => { - const { children } = stats.compilation; + const { children, warnings } = stats.compilation; if (Array.isArray(children)) { stats.compilation.children = children.filter(child => this.shouldPickStatChild(child)); } - // if (Array.isArray(warnings)) { - // stats.compilation.warnings = warnings.filter(message => this.shouldPickWarning(message)); - // } + if (Array.isArray(warnings)) { + stats.compilation.warnings = warnings.filter(message => this.shouldPickWarning(message)); + } }); } } diff --git a/antd-tools/utils/get-npm-args.js b/antd-tools/utils/get-npm-args.js index 9de9013c1..2e11613cc 100644 --- a/antd-tools/utils/get-npm-args.js +++ b/antd-tools/utils/get-npm-args.js @@ -2,6 +2,11 @@ // NOTE: the following code was partially adopted from https://github.com/iarna/in-publish module.exports = function getNpmArgs() { + // https://github.com/iarna/in-publish/pull/14 + if (process.env.npm_command) { + return [process.env.npm_command]; + } + let npmArgv = null; try { diff --git a/antd-tools/utils/getRunCmdEnv.js b/antd-tools/utils/getRunCmdEnv.js index c7b474bb3..12e326050 100644 --- a/antd-tools/utils/getRunCmdEnv.js +++ b/antd-tools/utils/getRunCmdEnv.js @@ -1,6 +1,7 @@ 'use strict'; const path = require('path'); +const isWindows = require('is-windows'); module.exports = function getRunCmdEnv() { const env = {}; @@ -14,7 +15,9 @@ module.exports = function getRunCmdEnv() { .filter(v => v.slice(0, 1).pop().toLowerCase() === 'path') .forEach(v => { const key = v.slice(0, 1).pop(); - env[key] = env[key] ? `${nodeModulesBinDir}:${env[key]}` : nodeModulesBinDir; + env[key] = env[key] + ? `${nodeModulesBinDir}${isWindows() ? ';' : ':'}${env[key]}` + : nodeModulesBinDir; }); return env; }; diff --git a/antd-tools/utils/projectHelper.js b/antd-tools/utils/projectHelper.js index 79cdb57cc..9ade6c777 100644 --- a/antd-tools/utils/projectHelper.js +++ b/antd-tools/utils/projectHelper.js @@ -13,6 +13,7 @@ function resolve(moduleName) { // We need hack the require to ensure use package module first // For example, `typescript` is required by `gulp-typescript` but provided by `antd` +// we do not need for ant-design-vue let injected = false; function injectRequire() { if (injected) return; @@ -45,9 +46,35 @@ function getConfig() { return {}; } +/** + * 是否存在可用的browserslist config + * https://github.com/browserslist/browserslist#queries + * @returns + */ +function isThereHaveBrowserslistConfig() { + try { + const packageJson = require(getProjectPath('package.json')); + if (packageJson.browserslist) { + return true; + } + } catch (e) { + // + } + if (fs.existsSync(getProjectPath('.browserslistrc'))) { + return true; + } + if (fs.existsSync(getProjectPath('browserslist'))) { + return true; + } + // parent项目的配置支持,需要再补充 + // ROWSERSLIST ROWSERSLIST_ENV 变量的形式,需要再补充。 + return false; +} + module.exports = { getProjectPath, resolve, injectRequire, getConfig, + isThereHaveBrowserslistConfig, }; diff --git a/antd-tools/utils/styleUtil.js b/antd-tools/utils/styleUtil.js new file mode 100644 index 000000000..7b05ee315 --- /dev/null +++ b/antd-tools/utils/styleUtil.js @@ -0,0 +1,11 @@ +// We convert less import in es/lib to css file path +function cssInjection(content) { + return content + .replace(/\/style\/?'/g, "/style/css'") + .replace(/\/style\/?"/g, '/style/css"') + .replace(/\.less/g, '.css'); +} + +module.exports = { + cssInjection, +}; diff --git a/components/version/index.ts b/components/version/index.ts index 882678218..36bbb7bee 100644 --- a/components/version/index.ts +++ b/components/version/index.ts @@ -1,5 +1,5 @@ -import pkg from '../../package.json'; - -const { version } = pkg; +/* eslint import/no-unresolved: 0 */ +// @ts-ignore +import version from './version'; export default version; diff --git a/package.json b/package.json index f4f004cd2..4fd345d35 100644 --- a/package.json +++ b/package.json @@ -30,14 +30,14 @@ ], "scripts": { "predev": "node node_modules/esbuild/install.js", - "dev": "yarn predev && yarn routes && vite serve site", + "dev": "npm run version && yarn predev && yarn routes && vite serve site", "test": "cross-env NODE_ENV=test jest --config .jest.js", "compile": "node antd-tools/cli/run.js compile", "generator-webtypes": "tsc -p antd-tools/generator-types/tsconfig.json && node antd-tools/generator-types/index.js", - "pub": "node --max_old_space_size=8192 antd-tools/cli/run.js pub", - "pub-with-ci": "node antd-tools/cli/run.js pub-with-ci", + "pub": "npm run version && node --max_old_space_size=8192 antd-tools/cli/run.js pub", + "pub-with-ci": "npm run version && node antd-tools/cli/run.js pub-with-ci", "prepublishOnly": "node antd-tools/cli/run.js guard", - "pre-publish": "node ./scripts/prepub && npm run generator-webtypes", + "pre-publish": "npm run generator-webtypes", "prettier": "prettier -c --write **/*", "pretty-quick": "pretty-quick", "dist": "node --max_old_space_size=8192 antd-tools/cli/run.js dist", @@ -54,8 +54,17 @@ "vue-tsc": "vue-tsc --noEmit", "site": "yarn routes && ./node_modules/vite/bin/vite.js build site --base=https://next.antdv.com/", "pub:site": "npm run site && node site/scripts/pushToOSS.js", - "prepare": "husky install" + "prepare": "husky install", + "version": "node ./scripts/generate-version", + "sort-api": "node antd-tools/cli/run.js sort-api-table" }, + "browserslist": [ + "> 0.5%", + "last 2 versions", + "Firefox ESR", + "not dead", + "not IE 11" + ], "repository": { "type": "git", "url": "git+https://github.com/vueComponent/ant-design-vue.git" @@ -162,6 +171,7 @@ "html-webpack-plugin": "^5.3.1", "husky": "^6.0.0", "ignore-emit-webpack-plugin": "^2.0.6", + "is-windows": "^1.0.2", "jest": "^26.0.0", "jest-environment-jsdom-fifteen": "^1.0.2", "jest-serializer-vue": "^2.0.0", @@ -174,6 +184,7 @@ "less-plugin-npm-import": "^2.1.0", "less-vars-to-js": "^1.3.0", "lint-staged": "^11.0.0", + "majo": "^0.10.1", "markdown-it": "^8.4.2", "markdown-it-anchor": "^8.0.4", "markdown-it-container": "^3.0.0", @@ -197,6 +208,10 @@ "query-string": "^7.0.1", "querystring": "^0.2.0", "raw-loader": "^4.0.2", + "remark-frontmatter": "^2.0.0", + "remark-parse": "^8.0.0", + "remark-stringify": "^8.0.0", + "remark-yaml-config": "^4.1.0", "reqwest": "^2.0.5", "rimraf": "^3.0.0", "rucksack-css": "^1.0.2", diff --git a/scripts/css-variable-sync.js b/scripts/css-variable-sync.js new file mode 100644 index 000000000..f46cb13c1 --- /dev/null +++ b/scripts/css-variable-sync.js @@ -0,0 +1,222 @@ +/** + * ZombieJ: Since we still need mainly maintain the `default.less`. Create a script that generate + * `variable.less` from the `default.less` + */ + +const fse = require('fs-extra'); +const path = require('path'); +const chalk = require('chalk'); + +const folderPath = path.resolve(__dirname, '..', 'components', 'style', 'themes'); +const targetPath = path.resolve(folderPath, 'variable.less'); + +const defaultContent = fse.readFileSync(path.resolve(folderPath, 'default.less'), 'utf8'); + +// const variableContent = fse.readFileSync( +// path.resolve(__dirname, '..', 'components', 'style', 'themes', 'variable.less'), +// 'utf8', +// ); + +let variableContent = defaultContent; + +function replaceVariable(key, value) { + variableContent = variableContent.replace(new RegExp(`@${key}:[^;]*;`), `@${key}: ${value};`); +} + +function replaceVariableContent(key, content) { + const lines = variableContent.split(/\n/); + const startIndex = lines.findIndex(line => line.includes(`[CSS-VARIABLE-REPLACE-BEGIN: ${key}]`)); + const endIndex = lines.findIndex(line => line.includes(`[CSS-VARIABLE-REPLACE-END: ${key}]`)); + + if (startIndex !== -1 && endIndex !== -1) { + variableContent = [...lines.slice(0, startIndex), content, ...lines.slice(endIndex + 1)].join( + '\n', + ); + } +} + +replaceVariable('theme', 'variable'); + +replaceVariableContent( + 'html-variables', + ` +html { + @base-primary: @blue-6; + + // ========= Primary Color ========= + --@{ant-prefix}-primary-color: @base-primary; + --@{ant-prefix}-primary-color-hover: color(~\`colorPalette('@{base-primary}', 5) \`); + --@{ant-prefix}-primary-color-active: color(~\`colorPalette('@{base-primary}', 7) \`); + --@{ant-prefix}-primary-color-outline: fade(@base-primary, @outline-fade); + + // Legacy + @legacy-primary-1: color(~\`colorPalette('@{base-primary}', 1) \`); + + --@{ant-prefix}-primary-1: @legacy-primary-1; + --@{ant-prefix}-primary-2: color(~\`colorPalette('@{base-primary}', 2) \`); + --@{ant-prefix}-primary-3: color(~\`colorPalette('@{base-primary}', 3) \`); + --@{ant-prefix}-primary-4: color(~\`colorPalette('@{base-primary}', 4) \`); + --@{ant-prefix}-primary-5: color(~\`colorPalette('@{base-primary}', 5) \`); + --@{ant-prefix}-primary-6: @base-primary; + --@{ant-prefix}-primary-7: color(~\`colorPalette('@{base-primary}', 7) \`); + + // Deprecated + --@{ant-prefix}-primary-color-deprecated-pure: ~''; + --@{ant-prefix}-primary-color-deprecated-l-35: lighten(@base-primary, 35%); + --@{ant-prefix}-primary-color-deprecated-l-20: lighten(@base-primary, 20%); + --@{ant-prefix}-primary-color-deprecated-t-20: tint(@base-primary, 20%); + --@{ant-prefix}-primary-color-deprecated-t-50: tint(@base-primary, 50%); + --@{ant-prefix}-primary-color-deprecated-f-12: fade(@base-primary, 12%); + --@{ant-prefix}-primary-color-active-deprecated-f-30: fade(@legacy-primary-1, 30%); + --@{ant-prefix}-primary-color-active-deprecated-d-02: darken(@legacy-primary-1, 2%); + + // ========= Success Color ========= + --@{ant-prefix}-success-color: @green-6; + --@{ant-prefix}-success-color-hover: color(~\`colorPalette('@{green-6}', 5) \`); + --@{ant-prefix}-success-color-active: color(~\`colorPalette('@{green-6}', 7) \`); + --@{ant-prefix}-success-color-outline: fade(@green-6, @outline-fade); + --@{ant-prefix}-success-color-deprecated-bg: ~\`colorPalette('@{green-6}', 1) \`; + --@{ant-prefix}-success-color-deprecated-border: ~\`colorPalette('@{green-6}', 3) \`; + + // ========== Error Color ========== + --@{ant-prefix}-error-color: @red-5; + --@{ant-prefix}-error-color-hover: color(~\`colorPalette('@{red-5}', 5) \`); + --@{ant-prefix}-error-color-active: color(~\`colorPalette('@{red-5}', 7) \`); + --@{ant-prefix}-error-color-outline: fade(@red-5, @outline-fade); + --@{ant-prefix}-error-color-deprecated-bg: ~\`colorPalette('@{red-5}', 1) \`; + --@{ant-prefix}-error-color-deprecated-border: ~\`colorPalette('@{red-5}', 3) \`; + + // ========= Warning Color ========= + --@{ant-prefix}-warning-color: @gold-6; + --@{ant-prefix}-warning-color-hover: color(~\`colorPalette('@{gold-6}', 5) \`); + --@{ant-prefix}-warning-color-active: color(~\`colorPalette('@{gold-6}', 7) \`); + --@{ant-prefix}-warning-color-outline: fade(@gold-6, @outline-fade); + --@{ant-prefix}-warning-color-deprecated-bg: ~\`colorPalette('@{gold-6}', 1) \`; + --@{ant-prefix}-warning-color-deprecated-border: ~\`colorPalette('@{gold-6}', 3) \`; + + // ========== Info Color =========== + --@{ant-prefix}-info-color: @base-primary; + --@{ant-prefix}-info-color-deprecated-bg: ~\`colorPalette('@{base-primary}', 1) \`; + --@{ant-prefix}-info-color-deprecated-border: ~\`colorPalette('@{base-primary}', 3) \`; +} +`.trim(), +); + +// >>> Primary +replaceVariable('primary-color', "~'var(--@{ant-prefix}-primary-color)'"); +replaceVariable('primary-color-hover', "~'var(--@{ant-prefix}-primary-color-hover)'"); +replaceVariable('primary-color-active', "~'var(--@{ant-prefix}-primary-color-active)'"); +replaceVariable('primary-color-outline', "~'var(--@{ant-prefix}-primary-color-outline)'"); + +replaceVariable('processing-color', '@primary-color'); + +// >>> Info +replaceVariable('info-color', "~'var(--@{ant-prefix}-info-color)'"); +replaceVariable('info-color-deprecated-bg', "~'var(--@{ant-prefix}-info-color-deprecated-bg)'"); +replaceVariable( + 'info-color-deprecated-border', + "~'var(--@{ant-prefix}-info-color-deprecated-border)'", +); + +// >>> Success +replaceVariable('success-color', "~'var(--@{ant-prefix}-success-color)'"); +replaceVariable('success-color-hover', "~'var(--@{ant-prefix}-success-color-hover)'"); +replaceVariable('success-color-active', "~'var(--@{ant-prefix}-success-color-active)'"); +replaceVariable('success-color-outline', "~'var(--@{ant-prefix}-success-color-outline)'"); +replaceVariable( + 'success-color-deprecated-bg', + "~'var(--@{ant-prefix}-success-color-deprecated-bg)'", +); +replaceVariable( + 'success-color-deprecated-border', + "~'var(--@{ant-prefix}-success-color-deprecated-border)'", +); + +// >>> Warning +replaceVariable('warning-color', "~'var(--@{ant-prefix}-warning-color)'"); +replaceVariable('warning-color-hover', "~'var(--@{ant-prefix}-warning-color-hover)'"); +replaceVariable('warning-color-active', "~'var(--@{ant-prefix}-warning-color-active)'"); +replaceVariable('warning-color-outline', "~'var(--@{ant-prefix}-warning-color-outline)'"); +replaceVariable( + 'warning-color-deprecated-bg', + "~'var(--@{ant-prefix}-warning-color-deprecated-bg)'", +); +replaceVariable( + 'warning-color-deprecated-border', + "~'var(--@{ant-prefix}-warning-color-deprecated-border)'", +); + +// >>> Error +replaceVariable('error-color', "~'var(--@{ant-prefix}-error-color)'"); +replaceVariable('error-color-hover', "~'var(--@{ant-prefix}-error-color-hover)'"); +replaceVariable('error-color-active', "~'var(--@{ant-prefix}-error-color-active)'"); +replaceVariable('error-color-outline', "~'var(--@{ant-prefix}-error-color-outline)'"); +replaceVariable('error-color-deprecated-bg', "~'var(--@{ant-prefix}-error-color-deprecated-bg)'"); +replaceVariable( + 'error-color-deprecated-border', + "~'var(--@{ant-prefix}-error-color-deprecated-border)'", +); + +// >>> Primary Level Color +replaceVariable('primary-1', "~'var(--@{ant-prefix}-primary-1)'"); +replaceVariable('primary-2', "~'var(--@{ant-prefix}-primary-2)'"); +replaceVariable('primary-3', "~'var(--@{ant-prefix}-primary-3)'"); +replaceVariable('primary-4', "~'var(--@{ant-prefix}-primary-4)'"); +replaceVariable('primary-5', "~'var(--@{ant-prefix}-primary-5)'"); +replaceVariable('primary-6', "~'var(--@{ant-prefix}-primary-6)'"); +replaceVariable('primary-7', "~'var(--@{ant-prefix}-primary-7)'"); + +// Link +replaceVariable('link-hover-color', '@primary-color-hover'); +replaceVariable('link-active-color', '@primary-color-active'); + +replaceVariable( + 'table-selected-row-hover-bg', + "~'var(--@{ant-prefix}-primary-color-active-deprecated-d-02)'", +); + +replaceVariable( + 'picker-basic-cell-hover-with-range-color', + "~'var(--@{ant-prefix}-primary-color-deprecated-l-35)'", +); +replaceVariable( + 'picker-date-hover-range-border-color', + "~'var(--@{ant-prefix}-primary-color-deprecated-l-20)'", +); + +replaceVariable( + 'calendar-column-active-bg', + "~'var(--@{ant-prefix}-primary-color-active-deprecated-f-30)'", +); + +replaceVariable( + 'slider-handle-color-focus', + "~'var(--@{ant-prefix}-primary-color-deprecated-t-20)'", +); +replaceVariable( + 'slider-handle-color-focus-shadow', + "~'var(--@{ant-prefix}-primary-color-deprecated-f-12)'", +); +replaceVariable( + 'slider-dot-border-color-active', + "~'var(--@{ant-prefix}-primary-color-deprecated-t-50)'", +); + +replaceVariable( + 'transfer-item-selected-hover-bg', + "~'var(--@{ant-prefix}-primary-color-active-deprecated-d-02)'", +); + +replaceVariable('alert-success-border-color', '@success-color-deprecated-border'); +replaceVariable('alert-success-bg-color', '@success-color-deprecated-bg'); +replaceVariable('alert-info-border-color', '@info-color-deprecated-border'); +replaceVariable('alert-info-bg-color', '@info-color-deprecated-bg'); +replaceVariable('alert-warning-border-color', '@warning-color-deprecated-border'); +replaceVariable('alert-warning-bg-color', '@warning-color-deprecated-bg'); +replaceVariable('alert-error-border-color', '@error-color-deprecated-border'); +replaceVariable('alert-error-bg-color', '@error-color-deprecated-bg'); + +fse.writeFileSync(targetPath, variableContent, 'utf8'); + +// eslint-disable-next-line no-console +console.log(chalk.green('Success! Replaced path:'), targetPath); diff --git a/scripts/generate-version.js b/scripts/generate-version.js new file mode 100644 index 000000000..0370a17df --- /dev/null +++ b/scripts/generate-version.js @@ -0,0 +1,10 @@ +const fs = require('fs-extra'); +const path = require('path'); + +const { version } = require('../package.json'); + +fs.writeFileSync( + path.join(__dirname, '..', 'components', 'version', 'version.tsx'), + `export default '${version}'`, + 'utf8', +); diff --git a/webpack.build.conf.js b/webpack.build.conf.js index c065f6b04..3341339b5 100644 --- a/webpack.build.conf.js +++ b/webpack.build.conf.js @@ -1,8 +1,36 @@ // This config is for building dist files +const chalk = require('chalk'); +const RemovePlugin = require('remove-files-webpack-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); +const { ESBuildMinifyPlugin } = require('esbuild-loader'); +const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin'); const getWebpackConfig = require('./antd-tools/getWebpackConfig'); -const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin'); const darkVars = require('./scripts/dark-vars'); +const compactVars = require('./scripts/compact-vars'); + +function injectLessVariables(config, variables) { + (Array.isArray(config) ? config : [config]).forEach(conf => { + conf.module.rules.forEach(rule => { + // filter less rule + if (rule.test instanceof RegExp && rule.test.test('.less')) { + const lessRule = rule.use[rule.use.length - 1]; + if (lessRule.options.lessOptions) { + lessRule.options.lessOptions.modifyVars = { + ...lessRule.options.lessOptions.modifyVars, + ...variables, + }; + } else { + lessRule.options.modifyVars = { + ...lessRule.options.modifyVars, + ...variables, + }; + } + } + }); + }); + + return config; +} function addLocales(webpackConfig) { let packageName = 'antd-with-locales'; @@ -22,13 +50,42 @@ function externalDayjs(config) { }; } +function injectWarningCondition(config) { + config.module.rules.forEach(rule => { + // Remove devWarning if needed + if (rule.test.test('test.tsx')) { + rule.use = [ + ...rule.use, + { + loader: 'string-replace-loader', + options: { + search: 'devWarning(', + replace: "if (process.env.NODE_ENV !== 'production') devWarning(", + }, + }, + ]; + } + }); +} + function processWebpackThemeConfig(themeConfig, theme, vars) { themeConfig.forEach(config => { externalDayjs(config); // rename default entry to ${theme} entry Object.keys(config.entry).forEach(entryName => { - config.entry[entryName.replace('antd', `antd.${theme}`)] = config.entry[entryName]; + const originPath = config.entry[entryName]; + let replacedPath = [...originPath]; + + // We will replace `./index` to `./index-style-only` since theme dist only use style file + if (originPath.length === 1 && originPath[0] === './index') { + replacedPath = ['./index-style-only']; + } else { + // eslint-disable-next-line no-console + console.log(chalk.red('🆘 Seems entry has changed! It should be `./index`')); + } + + config.entry[entryName.replace('antd', `antd.${theme}`)] = replacedPath; delete config.entry[entryName]; }); @@ -45,14 +102,41 @@ function processWebpackThemeConfig(themeConfig, theme, vars) { } }); - const themeReg = new RegExp(`${theme}(.min)?\\.js(\\.map)?$`); + // apply ${theme} less variables + injectLessVariables(config, vars); + // ignore emit ${theme} entry js & js.map file - config.plugins.push(new IgnoreEmitPlugin(themeReg)); + config.plugins.push( + new RemovePlugin({ + after: { + root: './dist', + include: [ + `antd.${theme}.js`, + `antd.${theme}.js.map`, + `antd.${theme}.min.js`, + `antd.${theme}.min.js.map`, + ], + log: false, + logWarning: false, + }, + }), + ); }); } -const webpackConfig = getWebpackConfig(false); -const webpackDarkConfig = getWebpackConfig(false); +const legacyEntryVars = { + 'root-entry-name': 'default', +}; +const webpackConfig = injectLessVariables(getWebpackConfig(false), legacyEntryVars); +const webpackDarkConfig = injectLessVariables(getWebpackConfig(false), legacyEntryVars); +const webpackCompactConfig = injectLessVariables(getWebpackConfig(false), legacyEntryVars); +const webpackVariableConfig = injectLessVariables(getWebpackConfig(false), { + 'root-entry-name': 'variable', +}); + +webpackConfig.forEach(config => { + injectWarningCondition(config); +}); if (process.env.RUN_ENV === 'PRODUCTION') { webpackConfig.forEach(config => { @@ -60,7 +144,13 @@ if (process.env.RUN_ENV === 'PRODUCTION') { addLocales(config); // Reduce non-minified dist files size config.optimization.usedExports = true; - + // use esbuild + if (process.env.ESBUILD || process.env.CSB_REPO) { + config.optimization.minimizer[0] = new ESBuildMinifyPlugin({ + target: 'es2015', + css: true, + }); + } config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: 'static', @@ -68,9 +158,25 @@ if (process.env.RUN_ENV === 'PRODUCTION') { reportFilename: '../report.html', }), ); + + if (!process.env.NO_DUP_CHECK) { + config.plugins.push( + new DuplicatePackageCheckerPlugin({ + verbose: true, + emitError: true, + }), + ); + } }); processWebpackThemeConfig(webpackDarkConfig, 'dark', darkVars); + processWebpackThemeConfig(webpackCompactConfig, 'compact', compactVars); + processWebpackThemeConfig(webpackVariableConfig, 'variable', {}); } -module.exports = webpackConfig.concat(webpackDarkConfig); +module.exports = [ + ...webpackConfig, + ...webpackDarkConfig, + ...webpackCompactConfig, + ...webpackVariableConfig, +]; diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 5e1fbf93c..000000000 --- a/webpack.config.js +++ /dev/null @@ -1,149 +0,0 @@ -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const VueLoaderPlugin = require('vue-loader/dist/plugin').default; -const WebpackBar = require('webpackbar'); -const path = require('path'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); - -const babelConfig = { - cacheDirectory: true, - presets: [ - [ - '@babel/preset-env', - { - targets: { - browsers: ['last 2 versions', 'Firefox ESR', '> 1%', 'not ie 11'], - }, - }, - ], - '@babel/preset-typescript', - ], - plugins: [ - [ - 'babel-plugin-import', - { - libraryName: 'ant-design-vue', - libraryDirectory: '', // default: lib - style: true, - }, - ], - ['@vue/babel-plugin-jsx', { mergeProps: false, enableObjectSlots: false }], - '@babel/plugin-proposal-optional-chaining', - '@babel/plugin-transform-object-assign', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-export-default-from', - '@babel/plugin-proposal-export-namespace-from', - '@babel/plugin-proposal-class-properties', - ], -}; - -/** @type {import('webpack').Configuration} */ - -module.exports = { - mode: 'development', - entry: { - app: './examples/index.js', - }, - stats: { - warningsFilter: /export .* was not found in/, - }, - module: { - rules: [ - { - test: /\.md$/, - loader: 'raw-loader', - }, - { - test: /\.(vue)$/, - loader: 'vue-loader', - }, - { - test: /\.(ts|tsx)?$/, - use: [ - { - loader: 'babel-loader', - options: babelConfig, - }, - { - loader: 'ts-loader', - options: { - transpileOnly: true, - appendTsSuffixTo: ['\\.vue$'], - happyPackMode: false, - }, - }, - ], - exclude: /node_modules/, - }, - { - test: /\.(js|jsx)$/, - loader: 'babel-loader', - exclude: /pickr.*js/, - options: babelConfig, - }, - { - test: /\.(png|jpg|gif|svg)$/, - loader: 'file-loader', - options: { - name: '[name].[ext]?[hash]', - }, - }, - { - test: /\.less$/, - use: [ - { loader: 'style-loader' }, - { - loader: 'css-loader', - options: { sourceMap: true }, - }, - { - loader: 'less-loader', - options: { - lessOptions: { - sourceMap: true, - javascriptEnabled: true, - }, - }, - }, - ], - }, - { - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: {}, - }, - 'css-loader', - ], - }, - ], - }, - resolve: { - alias: { - 'ant-design-vue/es': path.join(__dirname, './components'), - 'ant-design-vue': path.join(__dirname, './components'), - vue$: 'vue/dist/vue.runtime.esm-bundler.js', - }, - extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.md'], - }, - devServer: { - historyApiFallback: { - rewrites: [{ from: /./, to: '/index.html' }], - }, - hot: true, - open: true, - }, - devtool: 'inline-cheap-module-source-map', - plugins: [ - new MiniCssExtractPlugin({ - filename: '[name].css', - }), - new HtmlWebpackPlugin({ - template: 'examples/index.html', - filename: 'index.html', - inject: true, - }), - new VueLoaderPlugin(), - new WebpackBar(), - ], -};