From d36b5dac0ac73dc580cc0a22c47438d6136e6b9e Mon Sep 17 00:00:00 2001 From: Aleksandr Kitov Date: Wed, 30 Mar 2022 17:35:28 +0300 Subject: [PATCH] feat(*): add support for ts override files --- packages/arui-scripts/README.md | 42 ++++++++++++--- packages/arui-scripts/package.json | 3 +- .../src/commands/build/build-wrapper.ts | 2 +- .../__tests__/update-with-presets.tests.ts | 6 +-- .../app-configs/available-package-settings.ts | 8 ++- .../src/configs/app-configs/get-defaults.ts | 5 +- .../src/configs/app-configs/index.ts | 2 + .../app-configs/update-with-presets.ts | 11 ++-- .../app-configs/validate-settings-keys.ts | 2 +- .../arui-scripts/src/configs/dev-server.ts | 3 +- .../arui-scripts/src/configs/stats-options.ts | 5 +- .../util/__tests__/apply-overrides.tests.ts | 3 -- .../src/configs/util/apply-overrides.ts | 52 ++++++++++++++++--- .../src/configs/util/register-ts-node.ts | 14 +++++ .../src/configs/webpack.client.dev.ts | 2 +- .../src/configs/webpack.client.prod.ts | 2 +- .../src/configs/webpack.server.prod.ts | 2 +- packages/arui-scripts/src/index.ts | 3 ++ packages/arui-scripts/tsconfig-local.json | 6 ++- 19 files changed, 134 insertions(+), 39 deletions(-) create mode 100644 packages/arui-scripts/src/configs/util/register-ts-node.ts create mode 100644 packages/arui-scripts/src/index.ts diff --git a/packages/arui-scripts/README.md b/packages/arui-scripts/README.md index 881d01e1..6333131a 100644 --- a/packages/arui-scripts/README.md +++ b/packages/arui-scripts/README.md @@ -443,7 +443,7 @@ arui-scripts bundle-analyze Если вам не хватает гибкости при использовании `arui-scripts`, например вы хотите добавить свой плагин для вебпака - вы можете воспользоваться механизмом `overrides`. -Для этого вам необходимо создать в корне вашего проекта файл `arui-scripts.overrides.js`, из которого вы сможете управлять +Для этого вам необходимо создать в корне вашего проекта файл `arui-scripts.overrides.js` или `arui-scripts.overrides.ts`, из которого вы сможете управлять конфигурацией почти всех инструментов, используемых в `arui-scripts`. Принцип работы тут следующий. Для всех конфигураций определен набор ключей, которые они будут искать в `arui-scripts.overrides.js`, @@ -451,7 +451,7 @@ arui-scripts bundle-analyze существующая конфигурация и полный конфиг приложения (см [AppConfig](./src/configs/app-configs/types.ts)). Возвращать такая функция должна так же конфигурацию. -Например такое содержимое `arui-scripts.overrides.js`: +Пример `arui-scripts.overrides.js`: ```javascript const path = require('path'); module.exports = { @@ -464,6 +464,25 @@ module.exports = { }; ``` +Пример `arui-scripts.overrides.ts`: +```ts +import type { OverrideFile } from 'arui-scripts'; +import path from 'path'; + +const overrides: OverrideFile = { + webpack: (config, applicationConfig) => { + config.resolve.alias = { + components: path.resolve(__dirname, 'src/components') + }; + return config; + } +}; + +export default overrides; +``` + +**В случае, если у вас на проекте лежит и ts, и js файл с overrides, использоваться будет js версия.** + С помощью этой конфигурации ко всем настройкам вебпака будет добавлен `alias` *components*. На данный момент можно переопределять следующие конфигурации: @@ -532,10 +551,10 @@ module.exports = { от папки, содержащей package.json. Так что это может быть как папка в проекте, так и пакет из node_modules). Сам пакет с пресетами может содержать два файла: -- `arui-scirpts.config.js` -- `arui-scripts.overrides.js` +- `arui-scirpts.config.js` (или `arui-scripts.config.ts`) +- `arui-scripts.overrides.js` (или `arui-scirpts.overrides.ts`) -### arui-scripts.config.js +### arui-scripts.config (js | ts) С помощью этого файла можно задать любые ключи [конфигурации](#настройки). ```js module.exports = { @@ -543,6 +562,17 @@ module.exports = { }; ``` +Или в виде ts: +```ts +import type { PackageSettings } from 'arui-scripts'; + +const settings: PackageSettings = { + baseDockerImage: 'my-company-artifactory.com/arui-scripts-base:11.2' +}; + +export default settings; +``` + На проекте конфиурация будет загружаться в следующем порядке: 1. базовые настройки из arui-scripts 2. настройки из presets @@ -555,7 +585,7 @@ module.exports = { они будут вычисляться относительно корня проекта, а не вашей конфигурации. Вы можете использовать абсолютные пути при необходимости задать путь до файла внутри пакета с пресетами. -### arui-scripts.overrides.js +### arui-scripts.overrides (js | ts) С помощью этого файла можно задать базовые оверрайды проекта, аналогично [заданию оверрайдов на проекте](#тонкая-настройка). ```js module.exports = { diff --git a/packages/arui-scripts/package.json b/packages/arui-scripts/package.json index 61f1c694..77ad3667 100644 --- a/packages/arui-scripts/package.json +++ b/packages/arui-scripts/package.json @@ -1,7 +1,8 @@ { "name": "arui-scripts", "version": "", - "main": "index.js", + "main": "./build/index.js", + "typings": "./build/index.d.ts", "license": "MPL-2.0", "repository": { "type": "git", diff --git a/packages/arui-scripts/src/commands/build/build-wrapper.ts b/packages/arui-scripts/src/commands/build/build-wrapper.ts index b57d1e78..ab1f943a 100644 --- a/packages/arui-scripts/src/commands/build/build-wrapper.ts +++ b/packages/arui-scripts/src/commands/build/build-wrapper.ts @@ -19,7 +19,7 @@ function build(config: webpack.Configuration, previousFileSizes?: unknown) { return reject(err); } const messages = formatWebpackMessages(stats?.toJson({})); - + if (messages.errors.length) { // Only keep the first error. Others are often indicative // of the same problem, but confuse the reader with noise. diff --git a/packages/arui-scripts/src/configs/app-configs/__tests__/update-with-presets.tests.ts b/packages/arui-scripts/src/configs/app-configs/__tests__/update-with-presets.tests.ts index e0cabfc3..5c2b5f50 100644 --- a/packages/arui-scripts/src/configs/app-configs/__tests__/update-with-presets.tests.ts +++ b/packages/arui-scripts/src/configs/app-configs/__tests__/update-with-presets.tests.ts @@ -16,7 +16,7 @@ describe('update-with-presets', () => { it('should add overrides path if preset contain overrides', () => { mockedTryResolve.mockImplementation((path: string) => { - if (path.includes('/arui-scripts.config.js')) { + if (path.includes('/arui-scripts.config')) { return undefined; } return path as any; @@ -30,7 +30,7 @@ describe('update-with-presets', () => { const updatedConfig = updateWithPresets(baseConfig); expect(updatedConfig.overridesPath) - .toEqual(['presets/arui-scripts.overrides.js', 'package-overrides-path.js']); + .toEqual(['presets/arui-scripts.overrides', 'package-overrides-path.js']); }); it('should merge config with config from presets', () => { @@ -40,7 +40,7 @@ describe('update-with-presets', () => { }; }, { virtual: true }); mockedTryResolve.mockImplementation((path: string) => { - if (path.includes('/arui-scripts.config.js')) { + if (path.includes('/arui-scripts.config')) { return 'virtual-presets' as any; } return undefined; diff --git a/packages/arui-scripts/src/configs/app-configs/available-package-settings.ts b/packages/arui-scripts/src/configs/app-configs/available-package-settings.ts index db446cb7..6338aeb5 100644 --- a/packages/arui-scripts/src/configs/app-configs/available-package-settings.ts +++ b/packages/arui-scripts/src/configs/app-configs/available-package-settings.ts @@ -3,7 +3,7 @@ import { AppConfigs } from './types'; /** * Эти ключи из конфига будут обновляться из package.json при их наличии */ -export const availablePackageSettings: Array = [ +export const availablePackageSettings = [ 'dockerRegistry', 'baseDockerImage', 'serverEntry', @@ -29,4 +29,8 @@ export const availablePackageSettings: Array = [ 'statsOutputFilename', 'componentsTheme', 'keepCssVars', -]; +] as const; + +type ArrayElementType> = ArrayType[number]; + +export type PackageSettings = Partial>>; diff --git a/packages/arui-scripts/src/configs/app-configs/get-defaults.ts b/packages/arui-scripts/src/configs/app-configs/get-defaults.ts index 6fbc9b89..779a1005 100644 --- a/packages/arui-scripts/src/configs/app-configs/get-defaults.ts +++ b/packages/arui-scripts/src/configs/app-configs/get-defaults.ts @@ -1,6 +1,7 @@ import path from 'path'; import fs from 'fs'; import { AppConfigs } from './types'; +import { tryResolve } from '../util/try-resolve'; export function getDefaults(): AppConfigs { const CWD = process.cwd(); @@ -14,7 +15,7 @@ export function getDefaults(): AppConfigs { const absoluteNodeModulesPath = path.resolve(CWD, 'node_modules'); const projectTsConfigPath = path.join(CWD, 'tsconfig.json'); const yarnLockFilePath = path.join(CWD, 'yarn.lock'); - const overridesPath = path.join(CWD, 'arui-scripts.overrides.js'); + const overridesPath = tryResolve(path.join(CWD, 'arui-scripts.overrides')); const nginxConfFilePath = path.join(CWD, 'nginx.conf'); const dockerfileFilePath = path.join(CWD, 'Dockerfile'); @@ -57,7 +58,7 @@ export function getDefaults(): AppConfigs { serverPort: 3000, debug: false, - overridesPath: [overridesPath], + overridesPath: overridesPath ? [overridesPath] : [], statsOutputFilename: 'stats.json', componentsTheme: undefined, diff --git a/packages/arui-scripts/src/configs/app-configs/index.ts b/packages/arui-scripts/src/configs/app-configs/index.ts index cba7f9ea..36725853 100644 --- a/packages/arui-scripts/src/configs/app-configs/index.ts +++ b/packages/arui-scripts/src/configs/app-configs/index.ts @@ -1,3 +1,5 @@ +import '../util/register-ts-node'; + import { AppConfigs } from './types'; import { getDefaults } from './get-defaults'; import { updateWithEnv } from './update-with-env'; diff --git a/packages/arui-scripts/src/configs/app-configs/update-with-presets.ts b/packages/arui-scripts/src/configs/app-configs/update-with-presets.ts index 3171d124..5a652fc7 100644 --- a/packages/arui-scripts/src/configs/app-configs/update-with-presets.ts +++ b/packages/arui-scripts/src/configs/app-configs/update-with-presets.ts @@ -11,16 +11,19 @@ export function updateWithPresets(config: AppConfigs) { } const presetsConfigPath = tryResolve( - `${packageSettings.presets}/arui-scripts.config.js`, + `${packageSettings.presets}/arui-scripts.config`, { paths: [config.cwd] } ); const presetsOverridesPath = tryResolve( - `${packageSettings.presets}/arui-scripts.overrides.js`, + `${packageSettings.presets}/arui-scripts.overrides`, { paths: [config.cwd] } ); if (presetsConfigPath) { - const presetsSettings = require(presetsConfigPath); - + let presetsSettings = require(presetsConfigPath); + if (presetsSettings.__esModule) { + // ts-node импортирует esModules, из них надо вытягивать default именно так + presetsSettings = presetsSettings.default; + } validateSettingsKeys(availablePackageSettings, presetsSettings); config = merge(config, presetsSettings); } diff --git a/packages/arui-scripts/src/configs/app-configs/validate-settings-keys.ts b/packages/arui-scripts/src/configs/app-configs/validate-settings-keys.ts index 38ad15f7..7b1bd92e 100644 --- a/packages/arui-scripts/src/configs/app-configs/validate-settings-keys.ts +++ b/packages/arui-scripts/src/configs/app-configs/validate-settings-keys.ts @@ -3,7 +3,7 @@ * @param {string[]} availableKeys Массив допустимых настроек * @param settingsObject Объект с настройками */ -function validateSettingsKeys(availableKeys: string[], settingsObject: Record) { +function validateSettingsKeys(availableKeys: ReadonlyArray, settingsObject: Record) { Object.keys(settingsObject).forEach((setting) => { if (!availableKeys.includes(setting)) { console.warn( diff --git a/packages/arui-scripts/src/configs/dev-server.ts b/packages/arui-scripts/src/configs/dev-server.ts index 6eed0cd7..c45d939f 100644 --- a/packages/arui-scripts/src/configs/dev-server.ts +++ b/packages/arui-scripts/src/configs/dev-server.ts @@ -1,10 +1,9 @@ import path from 'path'; -import { Configuration } from 'webpack-dev-server'; import configs from './app-configs'; import applyOverrides from './util/apply-overrides'; import http from "http"; -const devServerConfig = applyOverrides('devServer', { +const devServerConfig = applyOverrides('devServer', { port: configs.clientServerPort, liveReload: false, client: { diff --git a/packages/arui-scripts/src/configs/stats-options.ts b/packages/arui-scripts/src/configs/stats-options.ts index af35ead6..8a78186d 100644 --- a/packages/arui-scripts/src/configs/stats-options.ts +++ b/packages/arui-scripts/src/configs/stats-options.ts @@ -1,6 +1,7 @@ +import type { WebpackOptionsNormalized } from 'webpack'; import applyOverrides from './util/apply-overrides'; -const statsOptions = applyOverrides('stats', { +const statsOptions: WebpackOptionsNormalized['stats'] = applyOverrides('stats', { // Add asset Information assets: false, // Add information about cached (not built) modules @@ -25,8 +26,6 @@ const statsOptions = applyOverrides('stats', { hash: false, // Add built modules information modules: true, - // Set the maximum number of modules to be shown - maxModules: 0, // Show dependencies and origin of warnings/errors (since webpack 2.5.0) moduleTrace: true, // Show performance hint when file size exceeds `performance.maxAssetSize` diff --git a/packages/arui-scripts/src/configs/util/__tests__/apply-overrides.tests.ts b/packages/arui-scripts/src/configs/util/__tests__/apply-overrides.tests.ts index 581b0de5..b9926db6 100644 --- a/packages/arui-scripts/src/configs/util/__tests__/apply-overrides.tests.ts +++ b/packages/arui-scripts/src/configs/util/__tests__/apply-overrides.tests.ts @@ -29,12 +29,9 @@ it('should throw an error when overrides is not a function', () => { }; }, { virtual: true }); - const consoleError = jest.spyOn(console, 'error'); - const applyOverrides = require('../apply-overrides').default; expect(() => applyOverrides('foo', {})).toThrowError(TypeError); - expect(consoleError).toHaveBeenCalled(); }); it('should call override function and update config', () => { diff --git a/packages/arui-scripts/src/configs/util/apply-overrides.ts b/packages/arui-scripts/src/configs/util/apply-overrides.ts index 039ddf7b..6e20ba9e 100644 --- a/packages/arui-scripts/src/configs/util/apply-overrides.ts +++ b/packages/arui-scripts/src/configs/util/apply-overrides.ts @@ -1,14 +1,53 @@ +import type { Configuration as WebpackConfiguration, WebpackOptionsNormalized } from 'webpack'; +import type { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'; import appConfigs from '../app-configs'; import { AppConfigs } from '../app-configs/types'; -type OverrideFile = Record any>; +type Overrides = { + webpack: WebpackConfiguration; + webpackClient: WebpackConfiguration; + webpackDev: WebpackConfiguration; + webpackClientDev: WebpackConfiguration; + webpackServer: WebpackConfiguration; + webpackServerDev: WebpackConfiguration; + webpackProd: WebpackConfiguration; + webpackClientProd: WebpackConfiguration; + webpackServerProd: WebpackConfiguration; + devServer: WebpackDevServerConfiguration; + stats: WebpackOptionsNormalized['stats']; + + babel: any; // TODO: где взять typedef-ы для бабеля? + babelClient: any; + babelServer: any; + + postcss: any[]; // TODO: где взять typedef-ы для postcss + browsers: string[]; + supportingBrowsers: string[]; + + Dockerfile: string; + nginx: string; + 'start.sh': string; +}; + +type OverrideFunction = (config: Overrides[K], appConfig: AppConfigs) => Overrides[K]; + +export type OverrideFile = { + [K in keyof Overrides]?: OverrideFunction; +} let overrides: Array = []; overrides = appConfigs.overridesPath.map(path => { try { - return require(path) + const requireResult = require(path); + if (requireResult.__esModule) { + // ts-node импортирует esModules, из них надо вытягивать default именно так + return requireResult.default; + } + return requireResult; } catch (e) { + console.error(`Unable to process override file "${path}"`); + console.log(e); return {}; } }); @@ -19,17 +58,18 @@ overrides = appConfigs.overridesPath.map(path => { * @param {Object} config Конфиг, к которому нужно применить оверрайды * @returns {*} */ -function applyOverrides(overridesKey: string | string[], config: T): T { +function applyOverrides(overridesKey: Key | Key[], config: Overrides[Key]): Overrides[Key] { if (typeof overridesKey === 'string') { overridesKey = [overridesKey]; } overridesKey.forEach(key => { overrides.forEach((override) =>{ if (override.hasOwnProperty(key)) { - if (typeof override[key] !== 'function') { - console.error(`Override ${key} must be a function`); + const overrideFn = override[key]; + if (typeof overrideFn !== 'function') { + throw new TypeError(`Override ${key} must be a function`) } - config = override[key](config, appConfigs); + config = overrideFn(config, appConfigs); } }); }); diff --git a/packages/arui-scripts/src/configs/util/register-ts-node.ts b/packages/arui-scripts/src/configs/util/register-ts-node.ts new file mode 100644 index 00000000..853e7364 --- /dev/null +++ b/packages/arui-scripts/src/configs/util/register-ts-node.ts @@ -0,0 +1,14 @@ +// Мы используем ts-node для работы c конфигами, описаными на ts +require('ts-node') + .register({ + transpileOnly: true, + ignore: [], + compilerOptions: { + target: 'ES2016', + module: 'CommonJS', + skipLibCheck: true, + allowSyntheticDefaultImports: true, + moduleResolution: 'node', + esModuleInterop: true, + }, + }); diff --git a/packages/arui-scripts/src/configs/webpack.client.dev.ts b/packages/arui-scripts/src/configs/webpack.client.dev.ts index 7c6b0d6e..bfa05c5e 100644 --- a/packages/arui-scripts/src/configs/webpack.client.dev.ts +++ b/packages/arui-scripts/src/configs/webpack.client.dev.ts @@ -37,7 +37,7 @@ function getSingleEntry(clientEntry: string[]) { // This is the development configuration. // It is focused on developer experience and fast rebuilds. // The production configuration is different and lives in a separate file. -const webpackClientDev = applyOverrides(['webpack', 'webpackClient', 'webpackDev', 'webpackClientDev'], { +const webpackClientDev = applyOverrides(['webpack', 'webpackClient', 'webpackDev', 'webpackClientDev'], { target: 'web', mode: 'development', // You may want 'eval' instead if you prefer to see the compiled output in DevTools. diff --git a/packages/arui-scripts/src/configs/webpack.client.prod.ts b/packages/arui-scripts/src/configs/webpack.client.prod.ts index 145081ca..94918cdd 100644 --- a/packages/arui-scripts/src/configs/webpack.client.prod.ts +++ b/packages/arui-scripts/src/configs/webpack.client.prod.ts @@ -34,7 +34,7 @@ function getSingleEntry(entryPoint: string[]) { } // This is the production configuration. -const config = applyOverrides(['webpack', 'webpackClient', 'webpackProd', 'webpackClientProd'], { +const config = applyOverrides(['webpack', 'webpackClient', 'webpackProd', 'webpackClientProd'], { mode: 'production', // You may want 'eval' instead if you prefer to see the compiled output in DevTools. devtool: 'cheap-module-source-map', diff --git a/packages/arui-scripts/src/configs/webpack.server.prod.ts b/packages/arui-scripts/src/configs/webpack.server.prod.ts index 8fdc9800..70c3a331 100644 --- a/packages/arui-scripts/src/configs/webpack.server.prod.ts +++ b/packages/arui-scripts/src/configs/webpack.server.prod.ts @@ -27,7 +27,7 @@ function getSingleEntry(entryPoint: string[]) { // This is the production configuration. // It compiles slowly and is focused on producing a fast and minimal bundle. // The development configuration is different and lives in a separate file. -const config = applyOverrides(['webpack', 'webpackServer', 'webpackProd', 'webpackServerProd'], { +const config = applyOverrides(['webpack', 'webpackServer', 'webpackProd', 'webpackServerProd'], { mode: 'production', // Don't attempt to continue if there are any errors. bail: true, diff --git a/packages/arui-scripts/src/index.ts b/packages/arui-scripts/src/index.ts new file mode 100644 index 00000000..0813e232 --- /dev/null +++ b/packages/arui-scripts/src/index.ts @@ -0,0 +1,3 @@ +export type { OverrideFile } from './configs/util/apply-overrides'; + +export type { PackageSettings } from './configs/app-configs/available-package-settings'; diff --git a/packages/arui-scripts/tsconfig-local.json b/packages/arui-scripts/tsconfig-local.json index 67be7990..41699d29 100644 --- a/packages/arui-scripts/tsconfig-local.json +++ b/packages/arui-scripts/tsconfig-local.json @@ -5,10 +5,12 @@ "module": "CommonJS", "skipLibCheck": true, "outDir": "./build", - "allowJs": true + "allowJs": true, + "declaration": true }, "include": [ "src/**/*.ts", "src/**/*.js" - ] + ], + "exclude": ["build"] }