Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ types
node_modules
storybook-static
*.log
styles
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
{
libraryName: 'antd',
style: false,
libraryDirectory: 'es',
},
],
],
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
"files": [
"cjs",
"esm",
"styles",
"types"
],
"sideEffects": [
"**/*.css"
],
"scripts": {
"build:clean": "rimraf cjs esm types",
"build:clean": "rimraf cjs esm styles types",
"build:styles": "node scripts/antd-styles.cjs",
"build:es": "rollup --config --bundleConfigAsCjs",
"build:ts": "tsc --declaration --declarationDir types --emitDeclarationOnly",
"build": "yarn run build:clean && yarn run build:es && yarn run build:ts",
"build": "yarn run build:clean && yarn run build:styles && yarn run build:es && yarn run build:ts",
"validate": "yarn run lint && yarn run typecheck && yarn run test && yarn run test:storybook",
"test": "jest",
"test:watch": "jest --watch",
Expand Down
109 changes: 84 additions & 25 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,97 @@
import path from 'path';

import { babel } from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import styles from 'rollup-plugin-styles';

// eslint-disable-next-line import/extensions
import pkg from './package.json';

const {
BASE,
hasStyleEntry,
styleClosure,
} = require('./scripts/antd-styles.cjs');

Check failure on line 16 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Require statement not part of import statement

const stylesDir = path.resolve('styles');
const dependencyNames = Object.keys(pkg.dependencies);

const isDependency = (id) =>

Check failure on line 21 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Arrow function not allowed as declaration in global/top-level-module scope
/^@babel\/runtime|^rc-picker/.test(id) ||

Check failure on line 22 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Unsafe argument of type `any` assigned to a parameter of type `string`
dependencyNames.some((name) => id === name || id.startsWith(`${name}/`));

// Imports the stylesheets of the antd components a module uses, so that bundlers only ship those
const antdStyleImports = () => ({

Check failure on line 26 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Arrow function not allowed as declaration in global/top-level-module scope
name: 'antd-style-imports',
resolveId(source) {
return source.startsWith(stylesDir) ? { id: source, external: true } : null;
},
renderChunk(code, chunk) {
const toStyles = path.posix.relative(
path.posix.dirname(chunk.fileName),

Check failure on line 33 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Unsafe argument of type `any` assigned to a parameter of type `string`
'../styles',
);
return {
code: code.split(`${stylesDir}/`).join(`${toStyles}/`),
map: null,
};
},
transform(code, id) {
if (id.includes('node_modules')) return null;
const used = new Set(
[...code.matchAll(/from ['"]antd\/(?:es|lib)\/([a-z-]+)/g)]
.map((match) => match[1])
.filter(hasStyleEntry),
);
if (used.size === 0) return null;
const stylesheets = new Set([BASE]);
used.forEach((name) =>
styleClosure(name).forEach((s) => stylesheets.add(s)),

Check failure on line 51 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Unsafe argument of type `any` assigned to a parameter of type `string`
);
const imports = [...stylesheets].map(
(s) => `import '${stylesDir}/${s}.css';`,
);
return { code: `${imports.join('\n')}\n${code}`, map: null };
},
});

const toEs = (id) => id.replace(/^antd\/lib\//, 'antd/es/');

Check failure on line 60 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Arrow function not allowed as declaration in global/top-level-module scope
const toLib = (id) => id.replace(/^antd\/es\//, 'antd/lib/');

Check failure on line 61 in rollup.config.js

View workflow job for this annotation

GitHub Actions / eslint

Arrow function not allowed as declaration in global/top-level-module scope

const output = {
preserveModules: true,
preserveModulesRoot: 'src',
sourcemap: true,
assetFileNames: '[name][extname]',
// npm pack drops every node_modules directory, so bundled dependencies need another folder name
entryFileNames: (chunk) => `${chunk.name.replace(/node_modules/g, 'vendor')}.js`,
};

export default {
input: 'src/index.ts',

output: [
{
file: pkg.main,
...output,
dir: 'cjs',
format: 'cjs',
sourcemap: true,
// https://rollupjs.org/configuration-options/#output-interop
// Necessary in order for jest in consumers to work
interop: 'auto',
assetFileNames: '[name][extname]',
exports: 'named',
paths: toLib,
},
{
file: pkg.module,
...output,
dir: 'esm',
format: 'esm',
sourcemap: true,
assetFileNames: '[name][extname]',
paths: toEs,
},
],

// this resolves to window in the browser, thus enabling caching in the antd code below
// (!) `this` has been rewritten to `undefined`
// https://rollupjs.org/guide/en/#error-this-is-undefined
// node_modules/antd/es/affix/index.js
// 7: import _typeof from "@babel/runtime/helpers/esm/typeof";
// 8:
// 9: var __decorate = this && this.__decorate || function (decorators, target, key, desc) {
context: 'this',

// https://www.npmjs.com/package/@rollup/plugin-babel#babelhelpers
external: [/@babel\/runtime/],
external: isDependency,

plugins: [
peerDepsExternal(),
Expand All @@ -48,17 +100,24 @@
typescript({
exclude: '**/*.(test|stories).(ts|tsx)',
}),
styles({
mode: ['extract', 'index.css'],
// Keeps url('#linearGradient-1') of antd's Empty instead of emitting it as an asset
url: false,
less: {
javascriptEnabled: true,
},
// The TS plugin leaves `import { X } from 'antd'` alone, this maps it to `antd/es/x`
babel({
babelHelpers: 'bundled',
extensions: ['.ts', '.tsx'],
include: 'src/**',
babelrc: false,
configFile: false,
plugins: [
[
'import',
{ libraryName: 'antd', libraryDirectory: 'es', style: false },
],
],
}),
babel({
// https://www.npmjs.com/package/@rollup/plugin-babel#babelhelpers
babelHelpers: 'runtime',
}),
antdStyleImports(),
],
};
97 changes: 97 additions & 0 deletions scripts/antd-styles.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const fs = require('fs');
const path = require('path');

const root = path.resolve(__dirname, '..');
const antdEs = path.join(root, 'node_modules/antd/es');

const BASE = 'base';

function hasStylesheet(component) {
return fs.existsSync(path.join(antdEs, component, 'style/index.less'));
}

function componentsWithStyles() {
return fs.readdirSync(antdEs).filter(hasStylesheet);
}

function hasStyleEntry(component) {
return fs.existsSync(path.join(antdEs, component, 'style/index.js'));
}

function directDependencies(component) {
const source = fs.readFileSync(
path.join(antdEs, component, 'style/index.js'),
'utf8',
);
return [...source.matchAll(/^import '\.\.\/\.\.\/([a-z-]+)\/style';/gm)].map(
(match) => match[1],
);
}

/** Dependencies first, so that the cascade matches the order of the full stylesheet. */
function styleClosure(component) {
const ordered = [];
const visited = new Set();
const visit = (name) => {
if (visited.has(name) || !hasStyleEntry(name)) return;
visited.add(name);
directDependencies(name).forEach(visit);
if (hasStylesheet(name)) ordered.push(name);
};
visit(component);
return [BASE, ...ordered];
}

function lessEntry(name) {
const target =
name === BASE
? 'antd/es/style/index.less'
: `antd/es/${name}/style/index.less`;
return [
"@import 'antd/es/style/themes/compact.less';",
`@import (multiple) '${path.join(root, 'src/antd-variables.less')}';`,
`@import '${path.join(root, 'node_modules', target)}';`,
`@import (multiple) '${path.join(root, 'src/antd-variables.less')}';`,
].join('\n');
}

async function buildStyles(outDir) {
const less = require('less');
const postcss = require('postcss');
const cssnano = require('cssnano');
fs.rmSync(outDir, { recursive: true, force: true });
fs.mkdirSync(outDir, { recursive: true });
const sizes = {};
for (const name of [BASE, ...componentsWithStyles()]) {
const { css } = await less.render(lessEntry(name), {
javascriptEnabled: true,
filename: path.join(root, 'src', `${name}.less`),
paths: [path.join(root, 'node_modules')],
plugins: [],
});
const minified = await postcss([cssnano({ preset: 'default' })]).process(
css,
{ from: undefined },
);
fs.writeFileSync(path.join(outDir, `${name}.css`), minified.css);
sizes[name] = minified.css.length;
}
return sizes;
}

module.exports = {
BASE,
componentsWithStyles,
hasStyleEntry,
styleClosure,
buildStyles,
};

if (require.main === module) {
buildStyles(path.join(root, 'styles')).then((sizes) => {
const total = Object.values(sizes).reduce((a, b) => a + b, 0);
console.log(
`built ${Object.keys(sizes).length} stylesheets, ${total} bytes`,
);
});
}
9 changes: 9 additions & 0 deletions scripts/limes-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
set -Eeuo pipefail
# Tag-like production build of the Limes worktree with Rspack stats written to /tmp/per-module-scripts/stats-<label>.json.
cd "$1"
docker compose run --rm -e WEBPACK_INCLUDE_SOURCE_MAPS=true -e NODE_ENV=production -e WEBPACK_INCLUDE_BUNDLE_ANALYZER=true limes-frontend-web \
sh -c 'rm -rf build && yarn rspack build --config=internals/rspack/rspack.prod.ts --json=stats.json' > "/tmp/per-module-scripts/limes-build-$2.log" 2>&1
[ -f build/stats.json ] && [ "$(ls build | wc -l)" -gt 1 ] || { jq -r ".errors[:3][].message" stats.json; exit 1; }
mv build/stats.json "/tmp/per-module-scripts/stats-$2.json"; rm -f stats.json
rm -rf "/tmp/per-module-scripts/build-$2" && cp -r build "/tmp/per-module-scripts/build-$2"
33 changes: 33 additions & 0 deletions scripts/route-load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sys, json, os, gzip, re
# Prints, per route chunk, the JS and CSS a first page load needs: initial chunks plus the route chunk and its siblings.
label = sys.argv[1]
stats = json.load(open(f'/tmp/per-module-scripts/stats-{label}.json'))
build = f'/tmp/per-module-scripts/build-{label}'
chunks = {c['id']: c for c in stats['chunks']}
def file_sizes(cid):
raw = gz = 0
for f in chunks[cid]['files']:
if not f.endswith('.js'): continue
data = open(os.path.join(build, f), 'rb').read()
raw += len(data); gz += len(gzip.compress(data, 6))
return raw, gz
css = {}
for m in stats['modules']:
if 'css-loader' in m['name'] and re.search(r'\.(css|less|scss)$', m['name']):
lib = 'react-components' in m['name']
for c in m.get('chunks', []):
a = css.setdefault(c, [0, 0]); a[0] += m['size']; a[1] += m['size'] if lib else 0
initial = [c for c in chunks if chunks[c]['initial']]
def total(ids):
raw = gz = cs = lib = 0
for c in ids:
r, g = file_sizes(c); raw += r; gz += g
cs += css.get(c, [0, 0])[0]; lib += css.get(c, [0, 0])[1]
return raw, gz, cs, lib
out = {'initial': total(initial)}
routes = [c for c in chunks if not chunks[c]['initial'] and any('routes.ts' in (o.get('moduleName') or '') for o in chunks[c]['origins'])]
for c in routes:
group = {c} | {s for s in chunks[c].get('siblings', []) if s in chunks and not chunks[s]['initial']}
out[c] = total(group)
json.dump(out, open(f'/tmp/per-module-scripts/route-load-{label}.json', 'w'))
print(label, 'routes', len(routes), 'initial', out['initial'])
8 changes: 4 additions & 4 deletions src/Autocomplete/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export {
AutoComplete as Autocomplete,
AutoCompleteProps as AutocompleteProps,
} from 'antd';
import AntdAutoComplete from 'antd/es/auto-complete';

export const Autocomplete: typeof AntdAutoComplete = AntdAutoComplete;
export type { AutoCompleteProps as AutocompleteProps } from 'antd/es/auto-complete';
1 change: 0 additions & 1 deletion src/DatePicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import generatePicker, {
PickerTimeProps as AntdPickerTimeProps,
RangePickerProps as AntdRangePickerProps,
} from 'antd/es/date-picker/generatePicker';
import 'antd/es/date-picker/style/index';
import {
format as formatDate,
parse as parseDate,
Expand Down
8 changes: 6 additions & 2 deletions src/Grid/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import 'antd/es/grid/style/index';
import AntdCol from 'antd/es/col';
import AntdRow from 'antd/es/row';

export { Row, Col, RowProps } from 'antd';
export type { RowProps } from 'antd/es/row';

export const Row: typeof AntdRow = AntdRow;
export const Col: typeof AntdCol = AntdCol;
17 changes: 17 additions & 0 deletions src/antd-variables.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@ant-prefix: mll-ant;

// The default is barely visible, darken it enough to be readable
@disabled-color: fade(#000, 85%);

// See node_modules/antd/lib/style/themes/compact.less for size variables

// Makes some components like tables even more dense, the default line height is excessive
@line-height-base: 1.337;

// Table
@table-padding-vertical: 4px;
@table-padding-horizontal: 4px;
@table-padding-vertical-md: 2px;
@table-padding-horizontal-md: 2px;
@table-padding-vertical-sm: 1px;
@table-padding-horizontal-sm: 1px;
20 changes: 2 additions & 18 deletions src/antd.less
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
@import '~antd/lib/style/themes/compact.less';
@import (multiple) './antd-variables.less';
@import '~antd/dist/antd.less';

@ant-prefix: mll-ant;

// The default is barely visible, darken it enough to be readable
@disabled-color: fade(#000, 85%);

// See node_modules/antd/lib/style/themes/compact.less for size variables

// Makes some components like tables even more dense, the default line height is excessive
@line-height-base: 1.337;

// Table
@table-padding-vertical: 4px;
@table-padding-horizontal: 4px;
@table-padding-vertical-md: 2px;
@table-padding-horizontal-md: 2px;
@table-padding-vertical-sm: 1px;
@table-padding-horizontal-sm: 1px;
@import (multiple) './antd-variables.less';
3 changes: 0 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// eslint-disable-next-line @mll-lab/no-global-styles
import './antd.less';

// Global configuration
export * from './theme';
export * from './Provider';
Expand Down
Loading