Skip to content

feat(overrides): allow typescript overrides and config files #200

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions packages/arui-scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,15 +443,15 @@ 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`,
В случае если такой ключ найден и это функция - она будет вызвана, и в качестве аргументов ей будут переданы
существующая конфигурация и полный конфиг приложения (см [AppConfig](./src/configs/app-configs/types.ts)).
Возвращать такая функция должна так же конфигурацию.

Например такое содержимое `arui-scripts.overrides.js`:
Пример `arui-scripts.overrides.js`:
```javascript
const path = require('path');
module.exports = {
Expand All @@ -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*.

На данный момент можно переопределять следующие конфигурации:
Expand Down Expand Up @@ -532,17 +551,28 @@ 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 = {
baseDockerImage: 'my-company-artifactory.com/arui-scripts-base:11.2'
};
```

Или в виде 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
Expand All @@ -555,7 +585,7 @@ module.exports = {
они будут вычисляться относительно корня проекта, а не вашей конфигурации.
Вы можете использовать абсолютные пути при необходимости задать путь до файла внутри пакета с пресетами.

### arui-scripts.overrides.js
### arui-scripts.overrides (js | ts)
С помощью этого файла можно задать базовые оверрайды проекта, аналогично [заданию оверрайдов на проекте](#тонкая-настройка).
```js
module.exports = {
Expand Down
3 changes: 2 additions & 1 deletion packages/arui-scripts/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/arui-scripts/src/commands/build/build-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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', () => {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AppConfigs } from './types';
/**
* Эти ключи из конфига будут обновляться из package.json при их наличии
*/
export const availablePackageSettings: Array<keyof AppConfigs> = [
export const availablePackageSettings = [
'dockerRegistry',
'baseDockerImage',
'serverEntry',
Expand All @@ -29,4 +29,8 @@ export const availablePackageSettings: Array<keyof AppConfigs> = [
'statsOutputFilename',
'componentsTheme',
'keepCssVars',
];
] as const;

type ArrayElementType<ArrayType extends ReadonlyArray<unknown>> = ArrayType[number];

export type PackageSettings = Partial<Pick<AppConfigs, ArrayElementType<typeof availablePackageSettings>>>;
5 changes: 3 additions & 2 deletions packages/arui-scripts/src/configs/app-configs/get-defaults.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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');

Expand Down Expand Up @@ -57,7 +58,7 @@ export function getDefaults(): AppConfigs {
serverPort: 3000,

debug: false,
overridesPath: [overridesPath],
overridesPath: overridesPath ? [overridesPath] : [],
statsOutputFilename: 'stats.json',

componentsTheme: undefined,
Expand Down
2 changes: 2 additions & 0 deletions packages/arui-scripts/src/configs/app-configs/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import '../util/register-ts-node';

import { AppConfigs } from './types';
import { getDefaults } from './get-defaults';
import { updateWithEnv } from './update-with-env';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @param {string[]} availableKeys Массив допустимых настроек
* @param settingsObject Объект с настройками
*/
function validateSettingsKeys(availableKeys: string[], settingsObject: Record<string, unknown>) {
function validateSettingsKeys(availableKeys: ReadonlyArray<string>, settingsObject: Record<string, unknown>) {
Object.keys(settingsObject).forEach((setting) => {
if (!availableKeys.includes(setting)) {
console.warn(
Expand Down
3 changes: 1 addition & 2 deletions packages/arui-scripts/src/configs/dev-server.ts
Original file line number Diff line number Diff line change
@@ -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<Configuration>('devServer', {
const devServerConfig = applyOverrides('devServer', {
port: configs.clientServerPort,
liveReload: false,
client: {
Expand Down
5 changes: 2 additions & 3 deletions packages/arui-scripts/src/configs/stats-options.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
52 changes: 46 additions & 6 deletions packages/arui-scripts/src/configs/util/apply-overrides.ts
Original file line number Diff line number Diff line change
@@ -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<string, (config: any, appConfig: AppConfigs) => 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<K extends keyof Overrides> = (config: Overrides[K], appConfig: AppConfigs) => Overrides[K];

export type OverrideFile = {
[K in keyof Overrides]?: OverrideFunction<K>;
}

let overrides: Array<OverrideFile> = [];

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 {};
}
});
Expand All @@ -19,17 +58,18 @@ overrides = appConfigs.overridesPath.map(path => {
* @param {Object} config Конфиг, к которому нужно применить оверрайды
* @returns {*}
*/
function applyOverrides<T = any>(overridesKey: string | string[], config: T): T {
function applyOverrides<Key extends keyof Overrides>(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);
}
});
});
Expand Down
14 changes: 14 additions & 0 deletions packages/arui-scripts/src/configs/util/register-ts-node.ts
Original file line number Diff line number Diff line change
@@ -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,
},
});
2 changes: 1 addition & 1 deletion packages/arui-scripts/src/configs/webpack.client.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.Configuration>(['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.
Expand Down
2 changes: 1 addition & 1 deletion packages/arui-scripts/src/configs/webpack.client.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function getSingleEntry(entryPoint: string[]) {
}

// This is the production configuration.
const config = applyOverrides<webpack.Configuration>(['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',
Expand Down
2 changes: 1 addition & 1 deletion packages/arui-scripts/src/configs/webpack.server.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.Configuration>(['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,
Expand Down
3 changes: 3 additions & 0 deletions packages/arui-scripts/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type { OverrideFile } from './configs/util/apply-overrides';

export type { PackageSettings } from './configs/app-configs/available-package-settings';
6 changes: 4 additions & 2 deletions packages/arui-scripts/tsconfig-local.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
"module": "CommonJS",
"skipLibCheck": true,
"outDir": "./build",
"allowJs": true
"allowJs": true,
"declaration": true
},
"include": [
"src/**/*.ts",
"src/**/*.js"
]
],
"exclude": ["build"]
}