├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .gitignoreconfig ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .huxy └── app.configs.js ├── .npmrc ├── .npmrcconfig ├── .prettierignore ├── .versionrc.js ├── LICENSE ├── README.md ├── __tests__ └── add.test.js ├── babel.config.js ├── bin └── huxy.js ├── commitlint.config.js ├── configs ├── fileList.js ├── getDirName.js ├── index.js ├── merge.js └── shared │ ├── .versionrc.js │ ├── babel.config.js │ ├── commitlint.config.js │ ├── eslint.config.js │ ├── jest.config.js │ ├── postcss.config.js │ ├── prettier.config.js │ └── stylelint.config.js ├── eslint.config.js ├── index.js ├── jest.config.js ├── package.json ├── postcss.config.js ├── prettier.config.js ├── scripts ├── appProxy.js ├── envConfigs.js ├── getIPs.js ├── index.js ├── pathToURL.js ├── server.js ├── webpack.config.js ├── webpack.development.js └── webpack.production.js ├── stylelint.config.js └── tsconfig.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | not ie < 11 3 | last 2 versions 4 | > 1% 5 | last 2 iOS versions 6 | not dead -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Optional npm cache directory 24 | .npm 25 | 26 | # Optional REPL history 27 | .node_repl_history 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | bower_components 36 | typings 37 | 38 | # Editors 39 | .idea 40 | *.iml 41 | 42 | # OS metadata 43 | .DS_Store 44 | Thumbs.db 45 | 46 | # test 47 | __snapshots__ 48 | 49 | # package-lock.json 50 | package-lock.json 51 | 52 | # pnpm-lock.yaml 53 | pnpm-lock.yaml 54 | 55 | # build 56 | dist 57 | build 58 | 59 | # redis 60 | *.rdb 61 | 62 | # pycache 63 | __pycache__ 64 | -------------------------------------------------------------------------------- /.gitignoreconfig: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Optional npm cache directory 24 | .npm 25 | 26 | # Optional REPL history 27 | .node_repl_history 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | bower_components 36 | typings 37 | 38 | # Editors 39 | .idea 40 | *.iml 41 | 42 | # OS metadata 43 | .DS_Store 44 | Thumbs.db 45 | 46 | # test 47 | __snapshots__ 48 | 49 | # package-lock.json 50 | package-lock.json 51 | 52 | # pnpm-lock.yaml 53 | pnpm-lock.yaml 54 | 55 | # build 56 | dist 57 | build 58 | 59 | # redis 60 | *.rdb 61 | 62 | # pycache 63 | __pycache__ 64 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx commitlint -e $1 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | 6 | npm run test -------------------------------------------------------------------------------- /.huxy/app.configs.js: -------------------------------------------------------------------------------- 1 | const app = { 2 | // HOST: 'http://localhost', // 本地运行 3 | PORT: 8080, // 本地开发环境端口 4 | PROD_PORT: 8081, // 本地生产环境端口 5 | PUBLIC_DIR: 'public', // public 文件路径 6 | BUILD_DIR: 'build', // 构建产物路径 7 | DEV_ROOT_DIR: '/', // 开发环境 basepath 8 | // PROD_ROOT_DIR: '/huxy', // 生产环境 basepath 9 | projectName: 'XX平台', // 名称,页面初始化 title 10 | /* PROXY: { // 代理配置 11 | url: 'http://127.0.0.1:9000', 12 | prefix: '/api', 13 | }, */ 14 | envConfigs: { // 全局环境变量 15 | name: '项目名', 16 | _id: '其它属性', 17 | }, 18 | }; 19 | 20 | const webpack = { // webpack 配置 21 | // ... // 基础配置 22 | dev: { // 开发环境配置 23 | // ... 24 | }, 25 | prod: { // 生产环境配置 26 | // ... 27 | }, 28 | }; 29 | 30 | export default { 31 | app, 32 | webpack, 33 | }; 34 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps = true -------------------------------------------------------------------------------- /.npmrcconfig: -------------------------------------------------------------------------------- 1 | legacy-peer-deps = true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | **/build 4 | **/draft 5 | -------------------------------------------------------------------------------- /.versionrc.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/version'; 2 | 3 | export default configs({ 4 | // customCfgs 5 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 yiru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## @huxy/pack 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ahyiru/pack/blob/develop/LICENSE) 4 | [![npm version](https://img.shields.io/npm/v/@huxy/pack.svg)](https://www.npmjs.com/package/@huxy/pack) 5 | [![npm](https://img.shields.io/npm/dt/@huxy/pack)](https://www.npmjs.com/package/@huxy/pack) 6 | [![](https://img.shields.io/badge/blog-ihuxy-blue.svg)](http://ihuxy.com/) 7 | 8 | `@huxy/pack` 是一个项目构建工具,集成了 `esbuild`、`eslint`、`stylelint`、`jest`、`commitlint`、`husky`、`standard-version`、`postcss`、`prettier`,提供开发环境、构建打包、本地启动服务、环境配置、代理配置等功能。使用简单方便,功能齐全,配置灵活,可自己添加需要的功能插件。 9 | 10 | 运行时会自动生成插件配置文件,如 `babel.config.js` 文件等,可自行修改覆盖。 11 | 12 | 生成项目配置文件 `.huxy/app.configs.js` ,用户自行配置即可。 13 | 14 | ### app.configs 15 | 16 | 用户环境配置: 17 | 18 | ```javascript 19 | const app = { 20 | // entry: 'app', // 项目入口目录,默认 app 文件夹 21 | // HOST: 'http://localhost', // 本地运行 22 | PORT: 8080, // 本地开发环境端口 23 | PROD_PORT: 8081, // 本地生产环境端口 24 | PUBLIC_DIR: 'public', // public 文件路径 25 | BUILD_DIR: 'build', // 构建产物路径 26 | DEV_ROOT_DIR: '/', // 开发环境 basepath 27 | // PROD_ROOT_DIR: '/huxy', // 生产环境 basepath 28 | projectName: 'XX平台', // 名称,页面初始化 title 29 | /* PROXY: { // 代理配置 30 | url: 'http://127.0.0.1:9000', 31 | prefix: '/api', 32 | }, */ 33 | envConfigs: { // 全局环境变量 34 | name: '项目名', 35 | _id: '其它属性', 36 | }, 37 | }; 38 | 39 | const webpack = { // webpack 配置 40 | // ... // 基础配置 41 | dev: { // 开发环境配置 42 | // ... 43 | }, 44 | prod: { // 生产环境配置 45 | // ... 46 | }, 47 | }; 48 | 49 | const nodeServer = app => { // 本地 nodejs 服务配置 50 | app.get('/local/test', (req, res, next) => { 51 | console.log(req); 52 | }); 53 | }; 54 | 55 | 56 | export default { 57 | app, 58 | webpack, 59 | nodeServer, 60 | }; 61 | 62 | // 例如 63 | 64 | export default { 65 | app, 66 | webpack: (rootPath, appPath) => ({ 67 | resolve: { 68 | alias: { 69 | '@huxy': `${rootPath}/playground/huxy`, 70 | }, 71 | }, 72 | prod: { 73 | copy: [ // 拷贝文件 74 | { 75 | from: `${appPath}/public/robots.txt`, 76 | to: `${appPath}/build/robots.txt`, 77 | }, 78 | ], 79 | buildConfigs: { 80 | target: 'es2018', 81 | minify: true, 82 | }, 83 | }, 84 | }), 85 | }; 86 | ``` 87 | 88 | - copy:构建完成拷贝文件或文件夹。 89 | - buildConfigs:打包资源配置,见 esbuild 配置项。 90 | 91 | `webpack` 配置项可以是一个配置对象,也可以是一个带有 `rootDir` 和 `appPath` 的回调函数。当要使用到路径时,请使用回调函数来获取根目录路径和 `app` 路径。 92 | 93 | ### 运行命令 94 | 95 | ``` 96 | "start": "pack start", 97 | "build": "pack run build", 98 | "analyze": "pack run analyze", 99 | "server": "pack run server", 100 | "test": "pack run test", 101 | ``` 102 | 103 | 其它 `npm` 命令: 104 | 105 | ``` 106 | "eslint": "pack eslint 'app/**/*.{js,jsx}'", // 或直接使用 eslint 107 | "eslint-common": "eslint 'common/**/*.{js,jsx}'", 108 | "stylelint": "stylelint 'app/**/*.{css,less}'", 109 | "lint-fix": "eslint --fix 'app/**/*.{js,jsx}' && stylelint --fix 'app/**/*.{css,less}'", 110 | "prettier": "prettier 'app/**/*' --write --ignore-unknown", 111 | "release": "standard-version" 112 | ``` 113 | 114 | ### 使用 115 | 116 | 新建一个项目,创建 `public` 和 `app` 目录。 117 | 118 | - public:静态资源目录,存放 `index.html` 和 `favicon` 等。可使用 `app.configs` 里面的 `PUBLIC_DIR` 来配置路径,默认 `public ` 放在 `app` 目录里面。 119 | - app:存放项目代码文件,也是入口文件夹。可自己命名,如 `src`,在配置文件里配置 `entry: 'src'` 即可。 120 | 121 | 安装 `@huxy/pack` 122 | 123 | ``` 124 | npm i -D @huxy/pack 125 | ``` 126 | 127 | 然后在 `package.json` 里面创建上面运行命令里的 `scripts` ,就可运行了。 128 | 129 | ***版本 0.6+ 使用了 `esmodule`,只需在 `package.json` 里面设置 `"type": "module"` 即可。*** -------------------------------------------------------------------------------- /__tests__/add.test.js: -------------------------------------------------------------------------------- 1 | const sum = (a, b) => a + b; 2 | 3 | describe('add', () => { 4 | it ('adds 1 + 2 to equal 3', () => { 5 | expect(sum(1, 2)).toBe(3); 6 | }); 7 | }); 8 | 9 | 10 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/babel'; 2 | 3 | export default api => configs(api, { 4 | // customCfgs 5 | }); -------------------------------------------------------------------------------- /bin/huxy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import starter from '../index.js'; 4 | 5 | starter(); 6 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/commitlint'; 2 | 3 | export default configs({ 4 | // customCfgs 5 | }); -------------------------------------------------------------------------------- /configs/fileList.js: -------------------------------------------------------------------------------- 1 | const configFileList = [ 2 | { 3 | name: 'eslint', 4 | path: './eslint.config.js', 5 | jsconfig: './.eslintrc.js', 6 | }, 7 | { 8 | name: 'stylelint', 9 | path: './stylelint.config.js', 10 | jsconfig: './.stylelintrc.js', 11 | }, 12 | { 13 | name: 'commitlint', 14 | path: './commitlint.config.js', 15 | jsconfig: './.commitlintrc.js', 16 | }, 17 | { 18 | name: 'babel', 19 | path: './babel.config.js', 20 | jsconfig: './.babelrc.js', 21 | }, 22 | { 23 | name: 'prettier', 24 | path: './prettier.config.js', 25 | jsconfig: './.prettierrc.js', 26 | }, 27 | { 28 | name: 'jest', 29 | path: './jest.config.js', 30 | }, 31 | { 32 | name: 'postcss', 33 | path: './postcss.config.js', 34 | }, 35 | { 36 | name: 'version', 37 | path: './.versionrc.js', 38 | }, 39 | { 40 | name: 'prettierignore', 41 | path: './.prettierignore', 42 | }, 43 | { 44 | name: 'browserslist', 45 | path: './.browserslistrc', 46 | }, 47 | { 48 | name: 'editorconfig', 49 | path: './.editorconfig', 50 | }, 51 | { 52 | name: 'npmrcconfig', 53 | path: './.npmrc', 54 | alias: './.npmrcconfig', 55 | }, 56 | { 57 | name: 'gitignoreconfig', 58 | path: './.gitignore', 59 | alias: './.gitignoreconfig', 60 | }, 61 | ]; 62 | 63 | export default configFileList; 64 | -------------------------------------------------------------------------------- /configs/getDirName.js: -------------------------------------------------------------------------------- 1 | import {fileURLToPath} from 'node:url'; 2 | import path from 'node:path'; 3 | 4 | const getDirName = url => { 5 | const filename = fileURLToPath(url); 6 | return path.dirname(filename); 7 | }; 8 | 9 | export default getDirName; 10 | -------------------------------------------------------------------------------- /configs/index.js: -------------------------------------------------------------------------------- 1 | import {resolve} from 'node:path'; 2 | 3 | import fs from 'fs-extra'; 4 | 5 | import configFileList from './fileList.js'; 6 | 7 | import getDirName from './getDirName.js'; 8 | 9 | const __dirname = getDirName(import.meta.url); 10 | 11 | const rootDir = process.cwd(); 12 | const packDir = resolve(__dirname, '../'); 13 | // const sharedDir = resolve(__dirname, './shared'); 14 | 15 | const oldCfgList = [ 16 | `import configs from '@huxy/pack/config/eslint';`, 17 | `import configs from '@huxy/pack/config/stylelint';`, 18 | `import configs from '@huxy/pack/config/commitlint';`, 19 | `import configs from '@huxy/pack/config/jest';`, 20 | `import configs from '@huxy/pack/config/postcss';`, 21 | `import configs from '@huxy/pack/config/babel';`, 22 | `import configs from '@huxy/pack/config/prettier';`, 23 | `import configs from '@huxy/pack/config/version';`, 24 | ]; 25 | 26 | const initAppConfig = async () => { 27 | await initAppFiles(); 28 | await initHuskyFiles(); 29 | await initTestFiles(); 30 | await initConfigFiles(); 31 | // await initGitignore(); 32 | }; 33 | 34 | const initConfigFile = async (userConfigs, huxyConfigs) => { 35 | const exists = await fs.pathExists(userConfigs); 36 | if (!exists) { 37 | try { 38 | await fs.copy(huxyConfigs, userConfigs); 39 | } catch (error) { 40 | console.error(error); 41 | } 42 | } else { 43 | const data = await fs.readFile(userConfigs, 'utf8'); 44 | const isOldCfg = oldCfgList.includes(data); 45 | if (isOldCfg) { 46 | try { 47 | await fs.remove(userConfigs); 48 | await fs.copy(huxyConfigs, userConfigs); 49 | } catch (error) { 50 | console.error(error); 51 | } 52 | } 53 | } 54 | }; 55 | 56 | const initConfigFiles = async () => { 57 | for (let i = 0, l = configFileList.length; i < l; i++) { 58 | const aliasname = configFileList[i].alias; 59 | const filename = configFileList[i].path; 60 | const jsconfig = configFileList[i].jsconfig; 61 | if (jsconfig) { 62 | const jsconfigpath = resolve(rootDir, jsconfig); 63 | const hasJsconfig = await fs.pathExists(jsconfigpath); 64 | if (hasJsconfig) { 65 | await fs.remove(jsconfigpath); 66 | } 67 | } 68 | await initConfigFile(resolve(rootDir, filename), resolve(packDir, aliasname || filename)); 69 | } 70 | }; 71 | 72 | const initAppFiles = async () => { 73 | await fs.ensureDir(resolve(rootDir, './.huxy')); 74 | await initConfigFile(resolve(rootDir, './.huxy/app.configs.js'), resolve(packDir, './.huxy/app.configs.js')); 75 | }; 76 | 77 | const initHuskyFiles = async () => { 78 | const exists = await fs.pathExists(resolve(rootDir, './.husky')); 79 | if (exists) { 80 | await initConfigFile(resolve(rootDir, './.husky/.gitignore'), resolve(packDir, './.husky/.gitignore')); 81 | await initConfigFile(resolve(rootDir, './.husky/commit-msg'), resolve(packDir, './.husky/commit-msg')); 82 | await initConfigFile(resolve(rootDir, './.husky/pre-commit'), resolve(packDir, './.husky/pre-commit')); 83 | } 84 | }; 85 | 86 | const initTestFiles = async () => { 87 | await fs.ensureDir(resolve(rootDir, './__tests__')); 88 | await initConfigFile(resolve(rootDir, './__tests__/add.test.js'), resolve(packDir, './__tests__/add.test.js')); 89 | }; 90 | 91 | /* const initGitignore = async () => { 92 | const exists = await fs.pathExists(resolve(rootDir, './.git')); 93 | if (exists) { 94 | await initConfigFile(resolve(rootDir, './.gitignore'), resolve(packDir, './gitignoreconfig')); 95 | } 96 | }; */ 97 | 98 | export default initAppConfig; 99 | -------------------------------------------------------------------------------- /configs/merge.js: -------------------------------------------------------------------------------- 1 | const getType = value => Object.prototype.toString.call(value).slice(8, -1).toLowerCase(); 2 | const isArray = value => getType(value) === 'array'; 3 | const isObject = value => getType(value) === 'object'; 4 | const hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj ?? {}, prop); 5 | 6 | const mergeArr = (base, extend, key = 'id') => { 7 | if (!isArray(base)) { 8 | return extend; 9 | } 10 | if (!isArray(extend)) { 11 | return base; 12 | } 13 | const sameItems = {}; 14 | [...base, ...extend].map(item => { 15 | const idKey = isObject(item) ? item[key] ?? JSON.stringify(item) : item; 16 | if (sameItems[idKey] === undefined) { 17 | sameItems[idKey] = item; 18 | } else { 19 | const oldItem = sameItems[idKey]; 20 | if (isObject(oldItem) && isObject(item)) { 21 | sameItems[idKey] = mergeObj(oldItem, item, key); 22 | } else if (isArray(oldItem) && isArray(item)) { 23 | sameItems[idKey] = mergeArr(oldItem, item, key); 24 | } else { 25 | sameItems[idKey] = item; 26 | } 27 | } 28 | }); 29 | return Object.keys(sameItems).map(v => sameItems[v]); 30 | }; 31 | 32 | const mergeObj = (base, extend, key = 'id') => { 33 | if (!isObject(base)) { 34 | return extend; 35 | } 36 | if (!isObject(extend)) { 37 | return base; 38 | } 39 | for (let k in extend) { 40 | if (hasProp(extend, k)) { 41 | if (isObject(base[k]) && isObject(extend[k])) { 42 | base[k] = mergeObj(base[k], extend[k], key); 43 | } else if (isArray(base[k]) && isArray(extend[k])) { 44 | base[k] = mergeArr(base[k], extend[k], key); 45 | } else { 46 | base[k] = extend[k]; 47 | } 48 | } else { 49 | Object.setPrototypeOf(base, {[k]: extend[k]}); 50 | } 51 | } 52 | return base; 53 | }; 54 | 55 | const merge = (target, ...extend) => { 56 | const handleMerge = isArray(target) ? mergeArr : mergeObj; 57 | extend.map(item => (target = handleMerge(target, item))); 58 | return target; 59 | }; 60 | 61 | export default merge; 62 | -------------------------------------------------------------------------------- /configs/shared/.versionrc.js: -------------------------------------------------------------------------------- 1 | import merge from '../merge.js'; 2 | 3 | const configs = { 4 | types: [ 5 | { type: 'feat', section: '✨ Features | 新功能' }, 6 | { type: 'fix', section: '🐛 Bug Fixes | Bug 修复' }, 7 | { type: 'init', section: '🎉 Init | 初始化' }, 8 | { type: 'docs', section: '✏️ Documentation | 文档' }, 9 | { type: 'style', section: '💄 Styles | 风格' }, 10 | { type: 'refactor', section: '♻️ Code Refactoring | 代码重构' }, 11 | { type: 'perf', section: '⚡ Performance Improvements | 性能优化' }, 12 | { type: 'test', section: '✅ Tests | 测试' }, 13 | { type: 'revert', section: '⏪ Revert | 回退' }, 14 | { type: 'build', section: '📦‍ Build System | 打包构建' }, 15 | { type: 'chore', section: '🚀 Chore | 构建/工程依赖/工具' }, 16 | { type: 'ci', section: '👷 Continuous Integration | CI 配置' }, 17 | ], 18 | }; 19 | 20 | export default (customCfgs = {}) => merge(configs, customCfgs); 21 | -------------------------------------------------------------------------------- /configs/shared/babel.config.js: -------------------------------------------------------------------------------- 1 | import merge from '../merge.js'; 2 | 3 | const configs = api => { 4 | // api.cache.using(() => !!process.env.isDev); 5 | 6 | const presets = [ 7 | [ 8 | '@babel/preset-env', 9 | { 10 | // modules: 'commonjs', 11 | // modules: false, 12 | // loose: true, 13 | bugfixes: true, 14 | useBuiltIns: 'usage', 15 | shippedProposals: true, 16 | corejs: { 17 | version: '3.39', 18 | proposals: true, 19 | }, 20 | }, 21 | ], 22 | [ 23 | '@babel/preset-react', 24 | { 25 | runtime: 'automatic', 26 | }, 27 | ], 28 | ]; 29 | 30 | const plugins = [ 31 | [ 32 | 'babel-plugin-react-compiler', 33 | // {}, 34 | ], 35 | [ 36 | '@babel/plugin-transform-runtime', 37 | { 38 | absoluteRuntime: false, 39 | helpers: true, 40 | regenerator: true, 41 | corejs: false, 42 | }, 43 | ], 44 | ]; 45 | 46 | const env = { 47 | development: {}, 48 | production: {}, 49 | test: {}, 50 | }; 51 | 52 | return { 53 | babelrc: false, 54 | configFile: false, 55 | assumptions: { 56 | noDocumentAll: true, 57 | noClassCalls: true, 58 | iterableIsArray: true, 59 | privateFieldsAsProperties: true, 60 | setPublicClassFields: true, 61 | }, 62 | targets: { 63 | node: 'current', 64 | browsers: process.env.isDev ? ['last 2 versions'] : ['>0.3%', 'not dead', 'not op_mini all'], 65 | esmodules: true, 66 | }, 67 | sourceType: 'unambiguous', 68 | presets, 69 | plugins, 70 | env, 71 | }; 72 | }; 73 | 74 | export default (api, customCfgs = {}) => merge(configs(api), customCfgs); 75 | -------------------------------------------------------------------------------- /configs/shared/commitlint.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | feat: feature 新功能,新需求 4 | fix: 修复 bug 5 | docs: 仅仅修改了文档,比如README, CHANGELOG, CONTRIBUTE等等 6 | style: 仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑 7 | perf: 性能优化 8 | refactor: 代码重构,没有加新功能或者修复bug 9 | test: 测试用例,包括单元测试、集成测试等 10 | chore: 改变构建流程、或者增加依赖库、工具等,包括打包和发布版本 11 | build: 影响构建系统或外部依赖项的更改(gulp,npm等) 12 | ci: 对CI配置文件和脚本的更改 13 | revert: 回滚到上一个版本 14 | conflict: 解决合并过程中 15 | 16 | */ 17 | 18 | import merge from '../merge.js'; 19 | 20 | const configs = { 21 | extends: ['@commitlint/config-conventional'], 22 | parserPreset: { 23 | parserOpts: { 24 | issuePrefixes: ['#'], 25 | }, 26 | }, 27 | rules: { 28 | 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'perf', 'refactor', 'test', 'chore', 'build', 'ci', 'revert', 'conflict']], 29 | 'type-case': [2, 'always', 'lower-case'], 30 | 'type-empty': [2, 'never'], 31 | 'scope-case': [0], 32 | 'scope-empty': [0], 33 | 'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], 34 | 'subject-empty': [2, 'never'], 35 | 'subject-full-stop': [2, 'never', '.'], 36 | 'header-max-length': [2, 'always', 100], 37 | }, 38 | prompt: { 39 | messages: { 40 | skip: ':跳过', 41 | max: '最多 %d 字符', 42 | min: '至少 %d 字符', 43 | emptyWarning: '不能为空', 44 | upperLimitWarning: '超出限制', 45 | lowerLimitWarning: '低于限制', 46 | }, 47 | questions: { 48 | type: { 49 | description: `选择您要提交的更改类型: `, 50 | enum: { 51 | feat: { 52 | description: '新功能', 53 | title: 'Features', 54 | emoji: '✨', 55 | }, 56 | fix: { 57 | description: 'Bug 修复', 58 | title: 'Bug Fixes', 59 | emoji: '🐛', 60 | }, 61 | docs: { 62 | description: '文档', 63 | title: 'Documentation', 64 | emoji: '📚', 65 | }, 66 | style: { 67 | description: '风格样式', 68 | title: 'Styles', 69 | emoji: '💎', 70 | }, 71 | refactor: { 72 | description: '代码重构', 73 | title: 'Code Refactoring', 74 | emoji: '📦', 75 | }, 76 | perf: { 77 | description: '性能优化', 78 | title: 'Performance Improvements', 79 | emoji: '🚀', 80 | }, 81 | test: { 82 | description: '测试', 83 | title: 'Tests', 84 | emoji: '🚨', 85 | }, 86 | build: { 87 | description: '打包构建', 88 | title: 'Builds', 89 | emoji: '🛠', 90 | }, 91 | ci: { 92 | description: 'CI 配置', 93 | title: 'Continuous Integrations', 94 | emoji: '⚙️', 95 | }, 96 | chore: { 97 | description: `构建/工程依赖/工具`, 98 | title: 'Chores', 99 | emoji: '♻️', 100 | }, 101 | revert: { 102 | description: '回退', 103 | title: 'Reverts', 104 | emoji: '🗑', 105 | }, 106 | }, 107 | }, 108 | scope: { 109 | description: 'What is the scope of this change (e.g. component or file name)', 110 | }, 111 | subject: { 112 | description: 'Write a short, imperative tense description of the change', 113 | }, 114 | body: { 115 | description: 'Provide a longer description of the change', 116 | }, 117 | isBreaking: { 118 | description: 'Are there any breaking changes?', 119 | }, 120 | breakingBody: { 121 | description: 'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself', 122 | }, 123 | breaking: { 124 | description: 'Describe the breaking changes', 125 | }, 126 | isIssueAffected: { 127 | description: 'Does this change affect any open issues?', 128 | }, 129 | issuesBody: { 130 | description: 'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself', 131 | }, 132 | issues: { 133 | description: 'Add issue references (e.g. "fix #123", "re #123".)', 134 | }, 135 | }, 136 | }, 137 | }; 138 | 139 | export default (customCfgs = {}) => merge(configs, customCfgs); 140 | -------------------------------------------------------------------------------- /configs/shared/eslint.config.js: -------------------------------------------------------------------------------- 1 | // import babelParser from '@babel/eslint-parser'; 2 | // import babelPlugin from '@babel/eslint-plugin'; 3 | import reactPlugin from 'eslint-plugin-react'; 4 | import reactHooksPlugin from 'eslint-plugin-react-hooks'; 5 | import reactCompilerPlugin from 'eslint-plugin-react-compiler'; 6 | import js from "@eslint/js"; 7 | import globals from "globals"; 8 | import {fixupPluginRules} from '@eslint/compat'; 9 | 10 | const configs = (customCfgs = []) => [ 11 | js.configs.recommended, 12 | ...customCfgs, 13 | { 14 | ignores: ['**/node_modules/', 'coverage/', '**/build/', '**/draft/'], 15 | }, 16 | { 17 | languageOptions: { 18 | ecmaVersion: 'latest', 19 | sourceType: 'module', 20 | // parser: babelParser, 21 | parserOptions: { 22 | ecmaFeatures: { 23 | jsx: true, 24 | }, 25 | requireConfigFile: false, 26 | babelOptions: { 27 | babelrc: false, 28 | configFile: false, 29 | presets: ['@babel/preset-react'], // '@babel/preset-env', 30 | }, 31 | }, 32 | globals: { 33 | ...globals.browser, 34 | // ...globals.commonjs, 35 | // ...globals.es2021, 36 | ...globals.node, 37 | ...globals.jest, 38 | ...globals.serviceworker, 39 | Atomics: 'readonly', 40 | SharedArrayBuffer: 'readonly', 41 | }, 42 | }, 43 | files: ['**/*.{js,jsx}'], 44 | plugins: { 45 | react: fixupPluginRules(reactPlugin), 46 | 'react-hooks': reactHooksPlugin, 47 | 'react-compiler': reactCompilerPlugin, 48 | }, 49 | rules: { 50 | 'strict': [2, 'never'], 51 | 'eqeqeq': [1, 'smart'], 52 | 'curly': [1, 'all'], 53 | 'no-console': 1, 54 | 'no-empty': 1, 55 | 'no-debugger': 1, 56 | 'no-extra-bind': 1, 57 | 'no-lone-blocks': 1, 58 | 'no-var': 2, 59 | 'no-unused-expressions': [1, {allowShortCircuit: true, allowTernary: true}], 60 | 'no-unused-vars': [1, {args: 'none', ignoreRestSiblings: true}], 61 | 'no-constant-condition': [2, {checkLoops: false}], 62 | 'no-undef': 2, 63 | 'no-restricted-globals': [2, 'event'], 64 | 65 | 'react/jsx-key': 2, 66 | 'react/jsx-pascal-case': 1, 67 | 'react/self-closing-comp': 2, 68 | 'react/require-render-return': 2, 69 | 'react/jsx-uses-vars': 2, 70 | 'react/jsx-no-undef': 2, 71 | 72 | 'react-hooks/rules-of-hooks': 2, 73 | 'react-hooks/exhaustive-deps': 1, 74 | 75 | 'react-compiler/react-compiler': 2, 76 | }, 77 | settings: { 78 | react: { 79 | pragma: 'React', 80 | version: 'detect', 81 | }, 82 | }, 83 | }, 84 | ]; 85 | 86 | export default configs; -------------------------------------------------------------------------------- /configs/shared/jest.config.js: -------------------------------------------------------------------------------- 1 | import merge from '../merge.js'; 2 | 3 | const configs = { 4 | verbose: true, 5 | testEnvironment: 'jsdom', 6 | coverageThreshold: { 7 | global: { 8 | branches: 0, 9 | functions: 0.6, 10 | lines: 10, 11 | statements: 10, 12 | } /* , 13 | './playground/': { 14 | branches: 40, 15 | statements: 40, 16 | } */, 17 | }, 18 | coveragePathIgnorePatterns: ['/node_modules/'], 19 | testRegex: '(/__tests__/.*\\.(test|spec))\\.(tsx?|jsx?)$', 20 | testPathIgnorePatterns: ['/build/', '/server/', '/playground/', '/draft/'], 21 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'mjs', 'json', 'node', 'vue'], 22 | modulePathIgnorePatterns: ['/node_modules/'], 23 | moduleNameMapper: { 24 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/__mocks__/fileMock.js', 25 | '\\.(css|less)$': '/__mocks__/styleMock.js', 26 | '@app(.*)$': '/app/$1', 27 | }, 28 | /* transform: { 29 | // '^.+\\.vue$': 'vue-jest', 30 | // '^.+\\.tsx?$': 'ts-jest', 31 | '^.+\\.jsx?$': 'babel-jest', 32 | }, */ 33 | transform: {}, 34 | transformIgnorePatterns: ['/node_modules/'], 35 | unmockedModulePathPatterns: ['/node_modules/react/', '/node_modules/react-dom/'], 36 | collectCoverage: true, 37 | extensionsToTreatAsEsm: ['.jsx', '.ts'], 38 | }; 39 | 40 | export default (customCfgs = {}) => merge(configs, customCfgs); 41 | -------------------------------------------------------------------------------- /configs/shared/postcss.config.js: -------------------------------------------------------------------------------- 1 | import merge from '../merge.js'; 2 | 3 | const configs = { 4 | // parser: 'sugarss', 5 | plugins: { 6 | // 'postcss-import': {}, 7 | 'postcss-preset-env': { 8 | stage: 2, 9 | features: { 10 | 'nesting-rules': true, 11 | 'double-position-gradients': false, 12 | }, 13 | // autoprefixer: {flexbox: 'no-2009'}, 14 | browsers: process.env.isDev ? ['last 2 versions'] : ['>0.3%', 'not dead', 'not op_mini all'], 15 | // importFrom: '@app/commons/global.css', 16 | }, 17 | autoprefixer: { 18 | grid: 'autoplace', 19 | }, 20 | // cssnano: {}, 21 | // tailwindcss: {}, 22 | // 'postcss-px-to-viewport': { 23 | // unitToConvert: 'px', 24 | // viewportWidth: 750, 25 | // unitPrecision: 6, 26 | // propList: ['*'], 27 | // viewportUnit: 'vw', 28 | // fontViewportUnit: 'vw', 29 | // selectorBlackList: [],//需要忽略的CSS选择器 30 | // minPixelValue: 1, 31 | // mediaQuery: true, 32 | // replace: true, 33 | // exclude: [/node_modules/], 34 | // include: undefined, 35 | // landscape: false, 36 | // landscapeUnit: 'vw', 37 | // landscapeWidth: 568, 38 | // }, 39 | }, 40 | }; 41 | 42 | export default (customCfgs = {}) => merge(configs, customCfgs); 43 | -------------------------------------------------------------------------------- /configs/shared/prettier.config.js: -------------------------------------------------------------------------------- 1 | import merge from '../merge.js'; 2 | 3 | const configs = { 4 | printWidth: 200, 5 | tabWidth: 2, 6 | singleQuote: true, 7 | semi: true, 8 | trailingComma: 'all', 9 | bracketSpacing: false, 10 | arrowParens: 'avoid', 11 | }; 12 | 13 | export default (customCfgs = {}) => merge(configs, customCfgs); -------------------------------------------------------------------------------- /configs/shared/stylelint.config.js: -------------------------------------------------------------------------------- 1 | import merge from '../merge.js'; 2 | 3 | const configs = { 4 | extends: 'stylelint-config-recommended', 5 | // extends: 'stylelint-config-standard', 6 | customSyntax: 'postcss-less', 7 | plugins: ['stylelint-order'], 8 | rules: { 9 | // 'selector-class-pattern': '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$',// 规定css类名格式(此处为短横线命名法,例如:.m-title) 10 | 'function-url-quotes': 'always', // url需要加引号 11 | 'selector-pseudo-class-no-unknown': [ 12 | true, 13 | { 14 | ignorePseudoClasses: ['global', 'export', 'import', 'local'], 15 | }, 16 | ], 17 | 'no-descending-specificity': null /* [ 18 | true, 19 | { 20 | ignore: ['selectors-within-list'], 21 | }, 22 | ] */, 23 | // 'no-invalid-double-slash-comments': true, 24 | 'selector-type-no-unknown': [ 25 | true, 26 | { 27 | ignoreTypes: ['/^page/'], 28 | }, 29 | ], 30 | 'declaration-property-value-no-unknown': null, 31 | }, 32 | ignoreFiles: ['**/*.js', '**/*.jsx', 'node_modules/**/*.css', 'coverage/**/*.css', '**/build/**/*.css', '**/draft/**/*.css'], 33 | // fix: true, 34 | }; 35 | 36 | export default (customCfgs = {}) => merge(configs, customCfgs); 37 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/eslint'; 2 | 3 | export default configs([ 4 | // customCfgs 5 | ]); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {resolve} from 'node:path'; 2 | 3 | import {spawn} from 'node:child_process'; 4 | 5 | // import colors from 'colors'; 6 | 7 | import getDirName from './configs/getDirName.js'; 8 | 9 | import initConfigs from './configs/index.js'; 10 | 11 | const __dirname = getDirName(import.meta.url); 12 | 13 | const starter = async () => { 14 | try { 15 | await initConfigs(); 16 | } catch (err) { 17 | console.error('init configs error: ', err); 18 | return; 19 | } 20 | 21 | const huxyDir = __dirname; 22 | 23 | const argvs = process.argv.slice(2); 24 | 25 | const argvStr = argvs.join(' '); 26 | 27 | const startStr = ['start', 'run start', 'run dev'].find(str => argvStr.startsWith(str)); 28 | if (startStr) { 29 | const cmdArgs = argvStr.replace(startStr, '').split(' ').filter(Boolean); 30 | const child = spawn('node', [resolve(huxyDir, 'scripts/index.js'), ...cmdArgs], {stdio: 'inherit'}); 31 | child.on('close', code => process.exit(code)); 32 | return; 33 | } 34 | if (argvStr.startsWith('run build')) { 35 | const cmdArgs = argvStr.replace('run build', '').split(' ').filter(Boolean); 36 | const child = spawn('webpack', ['--config', resolve(huxyDir, 'scripts/webpack.production.js'), '--progress', ...cmdArgs], {stdio: 'inherit'}); 37 | child.on('close', code => process.exit(code)); 38 | return; 39 | } 40 | if (argvStr.startsWith('run analyze')) { 41 | const cmdArgs = argvStr.replace('run analyze', '').split(' ').filter(Boolean); 42 | const child = spawn('webpack', ['--config', resolve(huxyDir, 'scripts/webpack.production.js'), '--progress', ...cmdArgs], { 43 | stdio: 'inherit', 44 | env: { 45 | ...process.env, 46 | ANALYZE: true, 47 | }, 48 | }); 49 | child.on('close', code => process.exit(code)); 50 | return; 51 | } 52 | if (argvStr.startsWith('run server')) { 53 | const cmdArgs = argvStr.replace('run server', '').split(' ').filter(Boolean); 54 | const child = spawn('node', [resolve(huxyDir, 'scripts/server.js'), ...cmdArgs], {stdio: 'inherit'}); 55 | child.on('close', code => process.exit(code)); 56 | return; 57 | } 58 | const testStr = ['test', 'run test', 'run jest'].find(str => argvStr.startsWith(str)); 59 | if (testStr) { 60 | const cmdArgs = argvStr.replace(testStr, '').split(' ').filter(Boolean); 61 | const child = spawn('jest', ['--colors', '--coverage', ...cmdArgs], {stdio: 'inherit'}); 62 | child.on('close', code => process.exit(code)); 63 | return; 64 | } 65 | const releaseStr = ['release', 'run release'].find(str => argvStr.startsWith(str)); 66 | if (releaseStr) { 67 | const cmdArgs = argvStr.replace(releaseStr, '').split(' ').filter(Boolean); 68 | const child = spawn('commit-and-tag-version', cmdArgs, {stdio: 'inherit'}); 69 | child.on('close', code => process.exit(code)); 70 | return; 71 | } 72 | 73 | const cmd = argvs[0]; 74 | const params = argvs.slice(1); 75 | 76 | const child = spawn(cmd, params, {stdio: 'inherit'}); 77 | 78 | child.on('close', code => process.exit(code)); 79 | 80 | /* child.stdout.on('data', data => { 81 | console.log(data.toString().blue); 82 | }); 83 | child.stderr.on('data', data => { 84 | console.error(data.toString().red); 85 | }); */ 86 | 87 | }; 88 | 89 | export default starter; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/jest'; 2 | 3 | export default configs({ 4 | // customCfgs 5 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@huxy/pack", 3 | "version": "1.7.7", 4 | "title": "scripts", 5 | "feature": "0.0.1", 6 | "hooks": "0.0.1", 7 | "type": "module", 8 | "description": "@huxy/pack 是一个项目构建工具,集成了 esbuild、eslint、stylelint、jest、commitlint、husky、standard-version、postcss、prettier,提供开发环境、构建打包、本地启动服务、环境配置、代理配置等功能。使用简单方便,功能齐全,配置灵活,可自己添加需要的功能插件。", 9 | "homepage": "https://github.com/ahyiru/pack", 10 | "keywords": [ 11 | "webpack", 12 | "node.js", 13 | "esbuild", 14 | "eslint", 15 | "stylelint", 16 | "jest", 17 | "commitlint", 18 | "husky", 19 | "prettier", 20 | "postcss", 21 | "standard-version", 22 | "scripts", 23 | "configs" 24 | ], 25 | "sideEffects": false, 26 | "main": "./index.js", 27 | "exports": { 28 | ".": "./index.js", 29 | "./package.json": "./package.json", 30 | "./config/eslint": "./configs/shared/eslint.config.js", 31 | "./config/stylelint": "./configs/shared/stylelint.config.js", 32 | "./config/commitlint": "./configs/shared/commitlint.config.js", 33 | "./config/babel": "./configs/shared/babel.config.js", 34 | "./config/prettier": "./configs/shared/prettier.config.js", 35 | "./config/jest": "./configs/shared/jest.config.js", 36 | "./config/postcss": "./configs/shared/postcss.config.js", 37 | "./config/version": "./configs/shared/.versionrc.js" 38 | }, 39 | "scripts": { 40 | "start": "node scripts/index.js", 41 | "build": "webpack --config scripts/webpack.production.js --progress", 42 | "analyze": "ANALYZE=1 webpack --config scripts/webpack.production.js --progress", 43 | "server": "node scripts/server.js --watch scripts/server.js", 44 | "test": "jest --colors --coverage", 45 | "eslint": "eslint 'scripts/**/*.{js,jsx}'", 46 | "stylelint": "stylelint 'scripts/**/*.{css,less}'", 47 | "lint": "npm run eslint && npm run stylelint", 48 | "lint-fix": "eslint --fix 'scripts/**/*.{js,jsx}' && stylelint --fix 'scripts/**/*.{css,less}'", 49 | "prettier": "prettier 'scripts/**/*' --write --ignore-unknown", 50 | "release": "commit-and-tag-version" 51 | }, 52 | "repository": { 53 | "type": "git", 54 | "url": "git+https://github.com/ahyiru/pack.git" 55 | }, 56 | "license": "MIT", 57 | "dependencies": { 58 | "@babel/core": "^7.27.3", 59 | "@babel/plugin-transform-runtime": "^7.27.3", 60 | "@babel/preset-env": "^7.27.2", 61 | "@babel/preset-react": "^7.27.1", 62 | "@babel/runtime": "^7.27.3", 63 | "@commitlint/cli": "^19.8.1", 64 | "@commitlint/config-conventional": "^19.8.1", 65 | "@eslint/compat": "^1.2.9", 66 | "@eslint/js": "^9.27.0", 67 | "@huxy/commit-and-tag-version": "^12.4.1", 68 | "@huxy/copy-file-webpack-plugin": "^1.1.5", 69 | "@huxy/open-browser-webpack-plugin": "^1.1.3", 70 | "@huxy/utils": "^2.1.14", 71 | "autoprefixer": "^10.4.21", 72 | "babel-loader": "^10.0.0", 73 | "babel-plugin-react-compiler": "^19.1.0-rc.2", 74 | "body-parser": "^2.2.0", 75 | "colors": "^1.4.0", 76 | "compression": "^1.8.0", 77 | "cors": "^2.8.5", 78 | "css-loader": "^7.1.2", 79 | "esbuild": "^0.25.5", 80 | "esbuild-loader": "^4.3.0", 81 | "eslint": "^9.27.0", 82 | "eslint-plugin-react": "^7.37.5", 83 | "eslint-plugin-react-compiler": "^19.1.0-rc.2", 84 | "eslint-plugin-react-hooks": "^5.2.0", 85 | "express": "^5.1.0", 86 | "file-loader": "^6.2.0", 87 | "fs-extra": "^11.3.0", 88 | "globals": "^16.2.0", 89 | "html-loader": "^5.1.0", 90 | "html-webpack-plugin": "^5.6.3", 91 | "http-proxy-middleware": "^3.0.5", 92 | "jest": "^29.7.0", 93 | "jest-environment-jsdom": "^29.7.0", 94 | "less": "^4.3.0", 95 | "less-loader": "^12.3.0", 96 | "mini-css-extract-plugin": "^2.9.2", 97 | "morgan": "^1.10.0", 98 | "postcss": "^8.5.4", 99 | "postcss-less": "^6.0.0", 100 | "postcss-loader": "^8.1.1", 101 | "postcss-preset-env": "^10.2.0", 102 | "prettier": "^3.5.3", 103 | "style-loader": "^4.0.0", 104 | "stylelint": "^16.20.0", 105 | "stylelint-config-recommended": "^16.0.0", 106 | "stylelint-order": "^7.0.0", 107 | "url-loader": "^4.1.1", 108 | "webpack": "^5.99.9", 109 | "webpack-bundle-analyzer": "^4.10.2", 110 | "webpack-cli": "^6.0.1", 111 | "webpack-dev-middleware": "^7.4.2", 112 | "webpack-hot-middleware": "^2.26.1", 113 | "webpack-merge": "^6.0.1", 114 | "workbox-webpack-plugin": "^7.3.0" 115 | }, 116 | "author": "yiru", 117 | "engines": { 118 | "node": ">=19.0.0" 119 | }, 120 | "bin": { 121 | "pack": "bin/huxy.js" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/postcss'; 2 | 3 | export default configs({ 4 | // customCfgs 5 | }); -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/prettier'; 2 | 3 | export default configs({ 4 | // customCfgs 5 | }); -------------------------------------------------------------------------------- /scripts/appProxy.js: -------------------------------------------------------------------------------- 1 | import {createProxyMiddleware} from 'http-proxy-middleware'; 2 | 3 | const proxyCfg = ({prefix = '/api', url, ...rest}) => ({ 4 | prefix, 5 | opts: { 6 | target: url, 7 | changeOrigin: true, 8 | pathRewrite: {'^/': `${prefix}/`}, 9 | // on: { 10 | // proxyReq: (proxyReq, req, res) => { 11 | // proxyReq.setHeader('clientip', req.ip); 12 | // }, 13 | // }, 14 | // xfwd: true, 15 | ...rest, 16 | }, 17 | }); 18 | 19 | const fixProxy = proxyItem => typeof proxyItem === 'string' ? {url: proxyItem} : proxyItem; 20 | 21 | const appProxy = (app, proxys) => { 22 | if (Array.isArray(proxys)) { 23 | proxys.map(proxyItem => { 24 | const {prefix, opts} = proxyCfg(fixProxy(proxyItem)); 25 | app.use(prefix, createProxyMiddleware(opts)); 26 | }); 27 | } else if (proxys) { 28 | const {prefix, opts} = proxyCfg(fixProxy(proxys)); 29 | app.use(prefix, createProxyMiddleware(opts)); 30 | } 31 | }; 32 | 33 | export default appProxy; 34 | -------------------------------------------------------------------------------- /scripts/envConfigs.js: -------------------------------------------------------------------------------- 1 | import {resolve} from 'node:path'; 2 | import pathToURL from './pathToURL.js'; 3 | 4 | const rootDir = process.cwd(); 5 | 6 | const configsPath = resolve(rootDir, './.huxy/app.configs.js'); 7 | 8 | const configs = (await import(pathToURL(configsPath))).default; 9 | 10 | const {webpack, entry} = configs; 11 | 12 | const appName = process.env.npm_config_dirname || entry || 'app'; 13 | 14 | const {HOST, PORT, PROD_PORT, PROXY, PUBLIC_DIR, BUILD_DIR, DEV_ROOT_DIR, PROD_ROOT_DIR, projectName, envConfigs} = configs[appName] || configs.app || {}; 15 | 16 | const devRoot = ['/', './'].includes(DEV_ROOT_DIR) ? '' : DEV_ROOT_DIR ?? ''; 17 | const prodRoot = ['/', './'].includes(PROD_ROOT_DIR) ? '' : PROD_ROOT_DIR ?? ''; 18 | 19 | const appPath = resolve(rootDir, appName); 20 | 21 | const publics = resolve(appPath, PUBLIC_DIR || 'public'); 22 | 23 | const buildPath = resolve(appPath, BUILD_DIR || 'build'); 24 | 25 | const webpackCfg = typeof webpack === 'function' ? webpack(rootDir, appPath) : webpack ?? {}; 26 | 27 | const {dev, prod, ...rest} = webpackCfg; 28 | 29 | const userConfigs = { 30 | rootDir, 31 | appName, 32 | HOST: HOST || 'http://localhost', 33 | PORT: PORT || 8080, 34 | PROD_PORT: PROD_PORT || 8081, 35 | PROXY, 36 | projectName: projectName || appName, 37 | envConfigs, 38 | devRoot, 39 | prodRoot, 40 | appPath, 41 | publics, 42 | buildPath, 43 | webpackCfg: rest || {}, 44 | webpackDevCfg: dev || {}, 45 | webpackProdCfg: prod || {}, 46 | configsPath, 47 | }; 48 | 49 | export default userConfigs; 50 | -------------------------------------------------------------------------------- /scripts/getIPs.js: -------------------------------------------------------------------------------- 1 | import os from 'node:os'; 2 | 3 | const getIPs = secure => { 4 | const protocol = secure ? 'https' : 'http'; 5 | const interfaces = os.networkInterfaces(); 6 | const arr = []; 7 | Object.keys(interfaces).map(key => arr.push(...interfaces[key])); 8 | const list = arr.filter(i => i.family === 'IPv4'); 9 | const ips = list.map(v => `${protocol}://${v.address}`); 10 | return ips; 11 | }; 12 | 13 | export default getIPs; 14 | -------------------------------------------------------------------------------- /scripts/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import webpack from 'webpack'; 3 | import colors from 'colors'; 4 | import {createServer} from 'node:http'; 5 | // import https from 'node:https'; 6 | // import fs from 'node:fs'; 7 | // import path from 'node:path'; 8 | 9 | import cors from 'cors'; 10 | import logger from 'morgan'; 11 | import bodyParser from 'body-parser'; 12 | import compression from 'compression'; 13 | 14 | import webpackDevMiddleware from 'webpack-dev-middleware'; 15 | import webpackHotMiddleware from 'webpack-hot-middleware'; 16 | 17 | import pathToURL from './pathToURL.js'; 18 | 19 | import getIPs from './getIPs.js'; 20 | 21 | import appProxy from './appProxy.js'; 22 | 23 | const webpackConfig = (await import('./webpack.development.js')).default; 24 | 25 | const {appName, HOST, PORT, PROXY, configsPath} = (await import('./envConfigs.js')).default; 26 | 27 | const {nodeServer} = (await import(pathToURL(configsPath))).default; 28 | 29 | const app = express(); 30 | 31 | const httpServer = createServer(app); 32 | 33 | appProxy(app, PROXY); 34 | 35 | const compiler = webpack(webpackConfig); 36 | 37 | const devMiddleware = webpackDevMiddleware(compiler, { 38 | publicPath: webpackConfig.output.publicPath, 39 | // outputFileSystem: {}, 40 | stats: { 41 | preset: 'minimal', 42 | moduleTrace: true, 43 | errorDetails: true, 44 | colors: true, 45 | }, 46 | }); 47 | 48 | app.use(webpackHotMiddleware(compiler)); 49 | app.use(devMiddleware); 50 | 51 | app.set('host', HOST); 52 | app.set('port', PORT); 53 | 54 | app.use(cors()); 55 | app.use(logger('dev')); 56 | app.use(bodyParser.json({limit: '20mb'})); 57 | app.use(bodyParser.urlencoded({limit: '20mb', extended: true})); 58 | app.use(compression()); 59 | 60 | if (typeof nodeServer === 'function' ) { 61 | nodeServer(app, httpServer); 62 | } 63 | 64 | // browserRouter 65 | app.get('/{*splat}', (req, res, next) => { 66 | const htmlBuffer = compiler.outputFileSystem.readFileSync(`${webpackConfig.output.path}/index.html`); 67 | res.set('Content-Type', 'text/html'); 68 | res.send(htmlBuffer); 69 | res.end(); 70 | }); 71 | 72 | httpServer.listen(app.get('port'), err => { 73 | if (err) { 74 | console.log(err); 75 | return false; 76 | } 77 | const ips = getIPs(true).map(ip => `${ip}:${app.get('port')}`).join('\n'); 78 | console.log('\n' + appName.green + ': 服务已启动! '.cyan + '✓'.green); 79 | console.log(`\n监听端口: ${app.get('port')} , 正在构建, 请稍后...构建完成后将自动打开浏览器`.cyan); 80 | console.log('-----------------------------------'.blue); 81 | console.log(`运行地址: \n`.green); 82 | console.log(`${ips} \n`.green); 83 | console.log(`如需打包部署到生产环境,请运行 `.cyan + `npm run build`.green); 84 | console.log('-----------------------------------'.blue); 85 | console.log('\n按下 CTRL-C 停止服务\n'.blue); 86 | }); 87 | -------------------------------------------------------------------------------- /scripts/pathToURL.js: -------------------------------------------------------------------------------- 1 | import {pathToFileURL} from 'node:url'; 2 | 3 | const pathToURL = path => pathToFileURL(path).href; 4 | 5 | export default pathToURL; 6 | -------------------------------------------------------------------------------- /scripts/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import colors from 'colors'; 3 | import cors from 'cors'; 4 | import logger from 'morgan'; 5 | import bodyParser from 'body-parser'; 6 | import compression from 'compression'; 7 | import path from 'node:path'; 8 | import {createServer} from 'node:http'; 9 | // import https from 'node:https'; 10 | // import fs from 'node:fs'; 11 | 12 | import pathToURL from './pathToURL.js'; 13 | 14 | import appProxy from './appProxy.js'; 15 | 16 | const {HOST, PROD_PORT, buildPath, PROXY, prodRoot, configsPath} = (await import('./envConfigs.js')).default; 17 | 18 | const {nodeServer} = (await import(pathToURL(configsPath))).default; 19 | 20 | const app = express(); 21 | 22 | const httpServer = createServer(app); 23 | 24 | appProxy(app, PROXY); 25 | 26 | app.set('host', HOST); 27 | app.set('port', PROD_PORT); 28 | 29 | app.use(cors()); 30 | app.use(logger('combined')); 31 | app.use(bodyParser.json({limit: '20mb'})); 32 | app.use(bodyParser.urlencoded({limit: '20mb', extended: true})); 33 | app.use(compression()); 34 | 35 | if (typeof nodeServer === 'function' ) { 36 | nodeServer(app, httpServer); 37 | } 38 | 39 | app.use(prodRoot || '/', express.static(buildPath)); 40 | app.get(`${prodRoot}/{*splat}`, (request, response) => { 41 | response.sendFile(path.resolve(buildPath, 'index.html')); 42 | }); 43 | 44 | /* const ssl = path.resolve(__dirname, './ssl'); 45 | const options = { 46 | key: fs.readFileSync(`${ssl}/ihuxy.com.key`), 47 | cert: fs.readFileSync(`${ssl}/ihuxy.com.pem`), 48 | }; 49 | const httpsServer = https.createServer(options, app); */ 50 | 51 | httpServer.listen(app.get('port'), err => { 52 | if (err) { 53 | console.log(err); 54 | return false; 55 | } 56 | console.log('\n服务已启动! '.black + '✓'.green); 57 | console.log(`\n监听端口: ${app.get('port')} ,正在构建,请稍后...`.cyan); 58 | console.log('-----------------------------------'.grey); 59 | console.log(` 本地地址: ${app.get('host')}:${app.get('port')}${prodRoot}`.green); 60 | console.log('-----------------------------------'.grey); 61 | console.log('\n按下 CTRL-C 停止服务\n'.blue); 62 | }); 63 | 64 | -------------------------------------------------------------------------------- /scripts/webpack.config.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | // import {fileURLToPath} from 'node:url'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import {merge} from 'webpack-merge'; 5 | import esbuild from 'esbuild'; 6 | 7 | const {rootDir, appPath, publics, projectName, buildPath, devRoot, webpackCfg} = (await import('./envConfigs.js')).default; 8 | 9 | const entry = { 10 | app: [path.resolve(appPath, 'index.jsx')], 11 | // ...frame, 12 | }; 13 | const templ = path.resolve(publics, 'index.html'); 14 | const icon = path.resolve(publics, 'favicon.png'); 15 | 16 | const htmlPlugin = () => 17 | new HtmlWebpackPlugin({ 18 | title: projectName, 19 | template: templ, 20 | favicon: icon, 21 | inject: true, 22 | scriptLoading: 'module', 23 | minify: { 24 | html5: true, 25 | collapseWhitespace: true, 26 | keepClosingSlash: true, 27 | removeComments: true, 28 | removeEmptyAttributes: true, 29 | removeRedundantAttributes: true, 30 | removeScriptTypeAttributes: true, 31 | removeStyleLinkTypeAttributes: true, 32 | useShortDoctype: true, 33 | }, 34 | }); 35 | 36 | const plugins = [ 37 | htmlPlugin(), 38 | ]; 39 | 40 | const rules = [ 41 | { 42 | test: /\.m?js/, 43 | resolve: { 44 | fullySpecified: false, 45 | }, 46 | exclude: [/node_modules/], 47 | }, 48 | { 49 | test: /\.jsx?$/, 50 | loader: 'esbuild-loader', 51 | options: { 52 | loader: 'jsx', 53 | target: 'esnext', 54 | jsx: 'automatic', 55 | tsconfigRaw: {}, 56 | implementation: esbuild, 57 | }, 58 | exclude: [/node_modules/], 59 | }, 60 | { 61 | test: /\.(jpe?g|png|gif|psd|bmp|ico|webp|svg|hdr)$/i, 62 | loader: 'url-loader', 63 | options: { 64 | limit: 20480, 65 | name: 'img/img_[hash:8].[ext]', 66 | // publicPath:'../', 67 | esModule: false, 68 | }, 69 | type: 'javascript/auto', 70 | exclude: [/node_modules/], 71 | }, 72 | { 73 | test: /\.(ttf|eot|svg|woff|woff2|otf)$/, 74 | loader: 'url-loader', 75 | options: { 76 | limit: 20480, 77 | name: 'fonts/[hash:8].[ext]', 78 | publicPath: '../', 79 | esModule: false, 80 | }, 81 | exclude: [/images/], 82 | }, 83 | { 84 | test: /\.html$/, 85 | use: { 86 | loader: 'html-loader', 87 | options: { 88 | minimize: true, 89 | }, 90 | }, 91 | include: [appPath], 92 | exclude: [/node_modules/, /public/], 93 | }, 94 | { 95 | test: /\.md$/, 96 | use: [ 97 | { 98 | loader: 'html-loader', 99 | options: { 100 | minimize: false, 101 | }, 102 | }, 103 | ], 104 | exclude: [/node_modules/], 105 | }, 106 | { 107 | test: /\.pdf$/, 108 | loader: 'url-loader', 109 | options: { 110 | limit: 20480, 111 | name: 'pdf/[hash].[ext]', 112 | }, 113 | exclude: [/node_modules/], 114 | }, 115 | { 116 | test: /\.(mp3|wav|mpeg|webm)$/, 117 | loader: 'url-loader', 118 | options: { 119 | limit: 20480, 120 | name: 'audio/[hash].[ext]', 121 | }, 122 | exclude: [/node_modules/], 123 | }, 124 | { 125 | test: /\.(mp4|m4a|swf|xap|mpeg|webm)$/, 126 | loader: 'url-loader', 127 | options: { 128 | limit: 40960, 129 | name: 'video/[hash].[ext]', 130 | }, 131 | exclude: [/node_modules/], 132 | }, 133 | { 134 | test: /\.(max|glb|gltf|fbx|stl|obj)$/, 135 | loader: 'url-loader', 136 | options: { 137 | limit: 40960, 138 | name: 'models/[hash].[ext]', 139 | }, 140 | exclude: [/node_modules/], 141 | }, 142 | ]; 143 | 144 | const baseConfigs = { 145 | context: appPath, 146 | cache: { 147 | type: 'filesystem', 148 | /* buildDependencies: { 149 | config: [fileURLToPath(import.meta.url)], 150 | }, */ 151 | }, 152 | experiments: { 153 | futureDefaults: true, 154 | topLevelAwait: true, 155 | // outputModule: true, 156 | asyncWebAssembly: true, 157 | layers: true, 158 | // lazyCompilation: true, 159 | }, 160 | node: { 161 | global: false, 162 | __filename: true, 163 | __dirname: true, 164 | }, 165 | entry: entry, 166 | output: { 167 | path: buildPath, 168 | publicPath: `${devRoot}/`, 169 | filename: 'js/[name].js', 170 | // module: true, 171 | }, 172 | optimization: { 173 | splitChunks: false, 174 | minimize: false, 175 | providedExports: false, 176 | usedExports: false, 177 | concatenateModules: false, 178 | sideEffects: 'flag', 179 | runtimeChunk: 'single', 180 | moduleIds: 'named', 181 | chunkIds: 'named', 182 | }, 183 | externals: {}, 184 | resolve: { 185 | modules: [appPath, 'node_modules'], 186 | alias: { 187 | '@app': appPath, 188 | }, 189 | extensions: ['.jsx', '.js', '.less', '.css', '.ts', '.tsx'], 190 | fallback: { 191 | path: false, //require.resolve('path-browserify'), 192 | fs: false, 193 | process: false, 194 | }, 195 | symlinks: false, 196 | cacheWithContext: false, 197 | }, 198 | module: { 199 | rules: rules, 200 | }, 201 | plugins: plugins, 202 | }; 203 | 204 | export default merge(baseConfigs, webpackCfg); 205 | -------------------------------------------------------------------------------- /scripts/webpack.development.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import webpack from 'webpack'; 3 | import {merge} from 'webpack-merge'; 4 | import OpenBrowserWebpackPlugin from '@huxy/open-browser-webpack-plugin'; 5 | 6 | const webpackBaseConfigs = (await import('./webpack.config.js')).default; 7 | 8 | const {rootDir, HOST, PORT, PROXY, envConfigs, devRoot, webpackDevCfg} = (await import('./envConfigs.js')).default; 9 | 10 | const devConfigs = { 11 | mode: 'development', 12 | devtool: 'eval-cheap-module-source-map', 13 | target: 'web', 14 | entry: { 15 | app: ['webpack-hot-middleware/client?reload=true'], 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | type: 'javascript/auto', 21 | test: /\.css$/, 22 | use: [ 23 | 'style-loader', 24 | { 25 | loader: 'css-loader', 26 | options: { 27 | importLoaders: 1, 28 | modules: { 29 | mode: 'global', 30 | localIdentName: '[path][name]__[local]--[hash:base64:5]', 31 | }, 32 | }, 33 | }, 34 | { 35 | loader: 'postcss-loader', 36 | options: {}, 37 | }, 38 | ], 39 | // exclude: [/node_modules/], 40 | }, 41 | { 42 | type: 'javascript/auto', 43 | test: /\.less$/, 44 | use: [ 45 | 'style-loader', 46 | { 47 | loader: 'css-loader', 48 | options: { 49 | importLoaders: 1, 50 | modules: { 51 | mode: 'global', 52 | localIdentName: '[path][name]__[local]--[hash:base64:5]', 53 | }, 54 | }, 55 | }, 56 | { 57 | loader: 'postcss-loader', 58 | options: {}, 59 | }, 60 | { 61 | loader: 'less-loader', 62 | options: { 63 | lessOptions: { 64 | javascriptEnabled: true, 65 | }, 66 | }, 67 | }, 68 | ], 69 | // exclude: [/node_modules/], 70 | }, 71 | /* { 72 | test: /\.s[ac]ss$/i, 73 | use: [ 74 | 'style-loader', 75 | { 76 | loader: 'css-loader', 77 | options: { 78 | importLoaders: 2, 79 | }, 80 | }, 81 | { 82 | loader: 'sass-loader', 83 | options: { 84 | implementation: require('sass'), 85 | sassOptions: { 86 | indentWidth: 2, 87 | }, 88 | additionalData: (content, loaderContext) => { 89 | if (loaderContext.resourcePath.endsWith('app/styles/index.scss')) { 90 | return content; 91 | } 92 | return `@import '~@app/styles/index.scss';${content};`; 93 | }, 94 | }, 95 | }, 96 | ], 97 | }, */ 98 | ], 99 | }, 100 | plugins: [ 101 | new webpack.HotModuleReplacementPlugin(), 102 | new webpack.DefinePlugin({ 103 | 'process.env': JSON.stringify({ 104 | configs: { 105 | basepath: devRoot, 106 | PROXY, 107 | buildTime: +new Date(), 108 | ...envConfigs, 109 | }, 110 | isDev: true, 111 | NODE_ENV: JSON.stringify('development'), 112 | }), 113 | EMAIL: JSON.stringify('ah.yiru@gmail.com'), 114 | VERSION: JSON.stringify('2.x.x'), 115 | }), 116 | new OpenBrowserWebpackPlugin({target: `${HOST}:${PORT}`}), 117 | ], 118 | }; 119 | 120 | export default merge(webpackBaseConfigs, devConfigs, webpackDevCfg); 121 | -------------------------------------------------------------------------------- /scripts/webpack.production.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import webpack from 'webpack'; 3 | import {merge} from 'webpack-merge'; 4 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 5 | 6 | import {EsbuildPlugin} from 'esbuild-loader'; 7 | 8 | import CopyFileWebpackPlugin from '@huxy/copy-file-webpack-plugin'; 9 | 10 | import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; 11 | 12 | import {GenerateSW} from 'workbox-webpack-plugin'; 13 | 14 | const webpackBaseConfigs = (await import('./webpack.config.js')).default; 15 | 16 | const {rootDir, appPath, publics, buildPath, PROXY, envConfigs, prodRoot, webpackProdCfg} = (await import('./envConfigs.js')).default; 17 | 18 | const {copy, buildConfigs, ...restProdCfg} = webpackProdCfg; 19 | 20 | const plugins = [ 21 | new webpack.optimize.ModuleConcatenationPlugin(), 22 | new webpack.optimize.MinChunkSizePlugin({ 23 | minChunkSize: 30000, 24 | }), 25 | new MiniCssExtractPlugin({ 26 | filename: 'css/[name]_[contenthash:8].css', 27 | chunkFilename: 'css/[id]_[name]_[contenthash:8].css', 28 | // publicPath:'../', 29 | }), 30 | new webpack.DefinePlugin({ 31 | 'process.env': JSON.stringify({ 32 | configs: { 33 | browserRouter: true, 34 | basepath: prodRoot, 35 | PROXY, 36 | buildTime: +new Date(), 37 | ...envConfigs, 38 | }, 39 | NODE_ENV: JSON.stringify('production'), 40 | }), 41 | EMAIL: JSON.stringify('ah.yiru@gmail.com'), 42 | VERSION: JSON.stringify('2.x.x'), 43 | }), 44 | new GenerateSW({ 45 | // importWorkboxFrom: 'local', 46 | // cacheId: 'huxy-pwa', 47 | skipWaiting: true, // 跳过 waiting 状态 48 | clientsClaim: true, // 通知让新的 sw 立即在页面上取得控制权 49 | cleanupOutdatedCaches: true, // 删除过时、老版本的缓存 50 | }), 51 | new CopyFileWebpackPlugin([ 52 | { 53 | from: path.resolve(publics, 'src'), 54 | to: path.resolve(appPath, `${buildPath}/src`), 55 | isDef: true, 56 | }, 57 | { 58 | from: path.resolve(publics, 'manifest.json'), 59 | to: path.resolve(appPath, `${buildPath}/manifest.json`), 60 | isDef: true, 61 | }, 62 | { 63 | from: path.resolve(publics, 'robots.txt'), 64 | to: path.resolve(appPath, `${buildPath}/robots.txt`), 65 | isDef: true, 66 | }, 67 | ...(Array.isArray(copy) ? copy : []), 68 | ]), 69 | /* new CompressionPlugin({ 70 | test: /\.(js|css)(\?.*)?$/i, 71 | filename: '[path].gz[query]', 72 | algorithm: 'gzip', 73 | threshold: 10240, 74 | minRatio: 0.8, 75 | deleteOriginalAssets: false, 76 | }), */ 77 | ]; 78 | 79 | if (process.env.ANALYZE) { 80 | plugins.push(new BundleAnalyzerPlugin()); 81 | } 82 | 83 | const prodConfigs = { 84 | mode: 'production', 85 | // devtool:'nosources-source-map', 86 | cache: false, 87 | experiments: { 88 | outputModule: true, 89 | }, 90 | output: { 91 | clean: true, 92 | path: buildPath, 93 | publicPath: `${prodRoot}/`, 94 | filename: 'js/[name]_[contenthash:8].js', 95 | chunkFilename: 'js/[name]_[chunkhash:8].chunk.js', 96 | module: true, 97 | }, 98 | optimization: { 99 | splitChunks: { 100 | chunks: 'all', //'async','initial' 101 | // minSize:0, 102 | minSize: { 103 | javascript: 5000, 104 | style: 5000, 105 | }, 106 | maxSize: { 107 | javascript: 500000, 108 | style: 500000, 109 | }, 110 | minChunks: 2, 111 | maxInitialRequests: 10, 112 | maxAsyncRequests: 10, 113 | // automaticNameDelimiter: '~', 114 | cacheGroups: { 115 | commons: { 116 | // chunks:'initial', 117 | // minSize:30000, 118 | idHint: 'commons', 119 | test: appPath, 120 | priority: 5, 121 | reuseExistingChunk: true, 122 | }, 123 | defaultVendors: { 124 | // chunks:'initial', 125 | idHint: 'vendors', 126 | test: /[\\/]node_modules[\\/]/, 127 | enforce: true, 128 | priority: 10, 129 | }, 130 | react: { 131 | idHint: 'react', 132 | test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, 133 | enforce: true, 134 | priority: 15, 135 | }, 136 | echarts: { 137 | idHint: 'echarts', 138 | chunks: 'all', 139 | priority: 20, 140 | test: ({context}) => context && (context.indexOf('echarts') >= 0 || context.indexOf('zrender') >= 0), 141 | }, 142 | three: { 143 | idHint: 'three', 144 | chunks: 'all', 145 | priority: 25, 146 | test: ({context}) => context && context.indexOf('three') >= 0, 147 | }, 148 | antd: { 149 | idHint: 'antd', 150 | chunks: 'all', 151 | priority: 30, 152 | test: ({context}) => context && (context.indexOf('@ant-design') >= 0 || context.indexOf('antd') >= 0), 153 | }, 154 | }, 155 | }, 156 | minimizer: [ 157 | /* new TerserPlugin({ 158 | // minify: TerserPlugin.esbuildMinify, 159 | parallel: true, 160 | extractComments: false, 161 | terserOptions: { 162 | ecma: 5, 163 | compress: { 164 | drop_console: true, 165 | }, 166 | format: { 167 | comments: false, 168 | }, 169 | parse: {}, 170 | mangle: true, 171 | module: false, 172 | }, 173 | }), 174 | new CssMinimizerPlugin({ 175 | // minify: CssMinimizerPlugin.esbuildMinify, 176 | parallel: true, 177 | minimizerOptions: { 178 | preset: [ 179 | 'default', 180 | { 181 | discardComments: {removeAll: true}, 182 | // calc: false, 183 | // normalizePositions: false, 184 | }, 185 | ], 186 | }, 187 | }), */ 188 | new EsbuildPlugin({ 189 | target: 'esnext', 190 | format: 'esm', 191 | css: true, // 缩小CSS 192 | minify: true, // 缩小JS 193 | minifyWhitespace: true, // 去掉空格 194 | minifyIdentifiers: true, // 缩短标识符 195 | minifySyntax: true, // 缩短语法 196 | legalComments: 'none', // 去掉注释 197 | // drop: ['console'], 198 | pure: ['console.log'], 199 | // implementation: esbuild, // 自定义 esbuild 版本 200 | ...buildConfigs, 201 | }), 202 | ], 203 | minimize: true, 204 | providedExports: true, 205 | usedExports: true, 206 | concatenateModules: false, 207 | sideEffects: true, 208 | runtimeChunk: false, 209 | moduleIds: 'deterministic', 210 | chunkIds: 'deterministic', 211 | }, 212 | module: { 213 | rules: [ 214 | { 215 | type: 'javascript/auto', 216 | test: /\.css$/, 217 | use: [ 218 | { 219 | loader: MiniCssExtractPlugin.loader, 220 | options: { 221 | // publicPath: '../', 222 | }, 223 | }, 224 | /* { 225 | loader:'isomorphic-style-loader', 226 | }, */ 227 | { 228 | loader: 'css-loader', 229 | options: { 230 | importLoaders: 1, 231 | modules: { 232 | mode: 'global', 233 | localIdentName: '[hash:base64:5]', 234 | }, 235 | }, 236 | }, 237 | { 238 | loader: 'postcss-loader', 239 | options: {}, 240 | }, 241 | ], 242 | // exclude: [/node_modules/], 243 | }, 244 | { 245 | type: 'javascript/auto', 246 | test: /\.less$/, 247 | use: [ 248 | { 249 | loader: MiniCssExtractPlugin.loader, 250 | options: { 251 | // publicPath: '../', 252 | }, 253 | }, 254 | { 255 | loader: 'css-loader', 256 | options: { 257 | importLoaders: 1, 258 | modules: { 259 | mode: 'global', 260 | localIdentName: '[hash:base64:5]', 261 | }, 262 | }, 263 | }, 264 | { 265 | loader: 'postcss-loader', 266 | options: {}, 267 | }, 268 | { 269 | loader: 'less-loader', 270 | options: { 271 | lessOptions: { 272 | javascriptEnabled: true, 273 | }, 274 | }, 275 | }, 276 | ], 277 | // exclude: [/node_modules/], 278 | }, 279 | /* { 280 | test: /\.s[ac]ss$/i, 281 | use: [ 282 | { 283 | loader: MiniCssExtractPlugin.loader, 284 | options: { 285 | // publicPath: '../', 286 | }, 287 | }, 288 | { 289 | loader: 'css-loader', 290 | options: { 291 | importLoaders: 2, 292 | }, 293 | }, 294 | { 295 | loader: 'sass-loader', 296 | options: { 297 | implementation: require('sass'), 298 | sassOptions: { 299 | indentWidth: 2, 300 | }, 301 | additionalData: (content, loaderContext) => { 302 | if (loaderContext.resourcePath.endsWith('app/styles/index.scss')) { 303 | return content; 304 | } 305 | return `@import '~@app/styles/index.scss';${content};`; 306 | }, 307 | }, 308 | }, 309 | ], 310 | }, */ 311 | ], 312 | }, 313 | plugins, 314 | }; 315 | 316 | export default merge(webpackBaseConfigs, prodConfigs, restProdCfg); 317 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | import configs from '@huxy/pack/config/stylelint'; 2 | 3 | export default configs({ 4 | // customCfgs 5 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "target": "esnext", 5 | "allowJs": true, 6 | "moduleResolution": "nodenext", 7 | "jsx": "preserve", 8 | "allowSyntheticDefaultImports": true, 9 | "noImplicitAny": false, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "pretty": true, 15 | "removeComments": true, 16 | "declaration": true, 17 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"], 18 | "typeRoots": ["/node_modules/@types"] 19 | }, 20 | "exclude": ["node_modules", "server", "**/draft/", "doc", "public", "build"] 21 | } 22 | --------------------------------------------------------------------------------