diff --git a/.circleci/config.yml b/.circleci/config.yml index e9d39f674c39..423fad65d679 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,7 @@ jobs: - run: name: Add examples/* to yarn workspace command: | + npm pkg delete workspaces[5] npm pkg delete workspaces[4] npm pkg delete workspaces[3] npm pkg delete workspaces[2] diff --git a/.github/workflows/bundle_size.yml b/.github/workflows/bundle_size.yml new file mode 100644 index 000000000000..18a373f53bf5 --- /dev/null +++ b/.github/workflows/bundle_size.yml @@ -0,0 +1,46 @@ +name: Bundle Size + +on: + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + paths: + - 'packages/**' + - 'yarn.lock' + - 'examples/test-bundlesize/**' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'yarn' + - name: Install packages + env: + YARN_ENABLE_IMMUTABLE_INSTALLS: true + run: | + # npm pkg delete workspaces[5] + # npm pkg delete workspaces[4] + # npm pkg delete workspaces[3] + # npm pkg delete workspaces[1] + corepack enable + yarn install + - name: Build packages + run: | + yarn workspaces foreach -Wptiv --no-private run build:lib + - name: compressed-size-action + uses: preactjs/compressed-size-action@v2 + continue-on-error: true + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + build-script: "build:sizecompare" + pattern: "examples/test-bundlesize/dist/**/*.{js,json}" + exclude: "{examples/test-bundlesize/dist/manifest.json,**/*.LICENSE.txt,**/*.map,**/node_modules/**}" + minimum-change-threshold: 8 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 8b74a374837d..000000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Compressed Size - -on: - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - paths: - - 'packages/**' - - 'yarn.lock' - -jobs: - compressed-size-build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - name: compressed-size-action - uses: preactjs/compressed-size-action@v2 - continue-on-error: true - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - build-script: "build:bundle" diff --git a/examples/test-bundlesize/.babelrc.js b/examples/test-bundlesize/.babelrc.js new file mode 100644 index 000000000000..217ee31f88bb --- /dev/null +++ b/examples/test-bundlesize/.babelrc.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@anansi', { polyfillMethod: false }], '@linaria'], +}; diff --git a/examples/test-bundlesize/.gitignore b/examples/test-bundlesize/.gitignore new file mode 100644 index 000000000000..620ba9e07f63 --- /dev/null +++ b/examples/test-bundlesize/.gitignore @@ -0,0 +1 @@ +dist-server \ No newline at end of file diff --git a/examples/test-bundlesize/LICENSE b/examples/test-bundlesize/LICENSE new file mode 100644 index 000000000000..b032e82432f3 --- /dev/null +++ b/examples/test-bundlesize/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 Nathaniel Tucker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/examples/test-bundlesize/package.json b/examples/test-bundlesize/package.json new file mode 100644 index 000000000000..45dd50b6213e --- /dev/null +++ b/examples/test-bundlesize/package.json @@ -0,0 +1,52 @@ +{ + "name": "test-bundlesize", + "version": "0.1.0", + "packageManager": "yarn@4.6.0", + "description": "Testing Bundled Size", + "scripts": { + "lint": "eslint src --ext .ts,.tsx", + "format": "npm run lint --fix", + "test:type": "tsc", + "start": "anansi serve --dev ./src/index.tsx", + "start:server": "anansi serve ./dist-server/App.js -a", + "build": "webpack --mode=production", + "build:server": "webpack --mode=production --target=node --env entrypath=index.server.tsx", + "build:analyze": "BABEL_DISABLE_CACHE=1 BROWSERSLIST_ENV='modern' webpack --mode=production --env analyze", + "build:sizecompare": "BABEL_DISABLE_CACHE=1 BROWSERSLIST_ENV='modern' webpack --mode=production --env nohash", + "build:profile": "webpack --mode=production --env profile", + "test:pkg": "webpack --env check=nobuild", + "postinstall": "rm -rf node_modules/.cache" + }, + "engines": { + "node": ">=18.0" + }, + "browserslist": "extends @anansi/browserslist-config", + "keywords": [ + "anansi" + ], + "devDependencies": { + "@anansi/babel-preset": "6.2.1", + "@anansi/browserslist-config": "^1.4.3", + "@anansi/webpack-config": "^20.0.0", + "@babel/core": "^7.22.15", + "@types/react": "*", + "@types/react-dom": "*", + "react-refresh": "*", + "typescript": "5.7.3", + "webpack": "*", + "webpack-cli": "*" + }, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.7", + "@data-client/img": "^0.14.0", + "@data-client/react": "^0.14.0", + "@data-client/rest": "^0.14.0", + "core-js": "^3.40.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "main": "src/index.ts", + "types": "src/index.ts", + "private": true, + "license": "MIT" +} diff --git a/examples/test-bundlesize/src/index.tsx b/examples/test-bundlesize/src/index.tsx new file mode 100644 index 000000000000..66ddac0a1d4d --- /dev/null +++ b/examples/test-bundlesize/src/index.tsx @@ -0,0 +1,31 @@ +import { AsyncBoundary, DataProvider, useSuspense } from '@data-client/react'; +import React from 'react'; +import { createRoot } from 'react-dom/client'; + +import { getCandles, TodoResource } from './resources'; + +export const Doit = () => { + const a = useSuspense(getCandles, { product_id: 'BTC-USD' }); + const todos = useSuspense(TodoResource.getList, { userId: 1 }); + return ( + <> + {a} hi {todos.length} + + ); +}; +export const second = React.memo(Doit); + +export default function Entry() { + return ( + + + + + + ); +} +export const renderedElement = ; + +createRoot(document.getElementById('root') || document.body).render( + renderedElement, +); diff --git a/examples/test-bundlesize/src/resources.ts b/examples/test-bundlesize/src/resources.ts new file mode 100644 index 000000000000..14049ef43f74 --- /dev/null +++ b/examples/test-bundlesize/src/resources.ts @@ -0,0 +1,67 @@ +import { Entity, resource, RestEndpoint, schema } from '@data-client/rest'; + +// docs: https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_getproductcandles +export const getCandles = new RestEndpoint({ + urlPrefix: 'https://api.exchange.coinbase.com', + path: '/products/:product_id/candles', + searchParams: {} as { + granularity?: 60 | 300 | 900 | 3600 | 21600 | 86400; + start?: string | number; + end?: string | number; + }, + process(value: CandleTuple[]) { + return value.map(candle => ({ + timestamp: candle[0], + price_open: candle[3], + })); + }, + pollFrequency: 60 * 1000, +}); + +type CandleTuple = [ + timestamp: number, + price_low: number, + price_high: number, + price_open: number, + price_close: number, +]; +export class Todo extends Entity { + id = 0; + userId = 0; + title = ''; + completed = false; +} +export const TodoResource = resource({ + urlPrefix: 'https://jsonplaceholder.typicode.com', + path: '/todos/:id', + searchParams: {} as { userId?: string | number } | undefined, + schema: Todo, + optimistic: true, +}); + +export class User extends Entity { + id = 0; + name = ''; + username = ''; + email = ''; + website = ''; + todos: Todo[] = []; + + get profileImage() { + return `https://i.pravatar.cc/64?img=${this.id + 4}`; + } + + static schema = { + todos: new schema.Collection([Todo], { + nestKey: (parent, key) => ({ + userId: parent.id, + }), + }), + }; +} +export const UserResource = resource({ + urlPrefix: 'https://jsonplaceholder.typicode.com', + path: '/users/:id', + schema: User, + optimistic: true, +}); diff --git a/examples/test-bundlesize/tsconfig.json b/examples/test-bundlesize/tsconfig.json new file mode 100644 index 000000000000..62357fd6becd --- /dev/null +++ b/examples/test-bundlesize/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "baseUrl": "./src", + "target": "esnext", + "module": "esnext", + "lib": [ + "dom", + "esnext" + ], + "jsx": "react-jsx", + "declaration": true, + "strict": true, + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true, + "types": [ + "@anansi/webpack-config/types" + ], + "noEmit": true + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/examples/test-bundlesize/webpack.config.js b/examples/test-bundlesize/webpack.config.js new file mode 100644 index 000000000000..6f8b33bfd476 --- /dev/null +++ b/examples/test-bundlesize/webpack.config.js @@ -0,0 +1,44 @@ +const { makeConfig } = require('@anansi/webpack-config'); + +const options = { + basePath: 'src', + buildDir: 'dist/', + serverDir: 'dist-server/', + globalStyleDir: 'style', + sassOptions: false, +}; + +const generateConfig = makeConfig(options); + +module.exports = (env, argv) => { + const config = generateConfig(env, argv); + if (!config.experiments) config.experiments = {}; + config.experiments.backCompat = false; + config.optimization.splitChunks = { + chunks: 'async', + maxInitialRequests: 3000, + maxAsyncRequests: 3000, + minSize: 1, + cacheGroups: { + react: { + test: /[\\/]node_modules[\\/](react|react-dom|scheduler|object-assign|loose-envify)[\\/]/, + name: 'react', + chunks: 'all', + }, + polyfill: { + test: /[\\/]node_modules[\\/](core-js|core-js-pure|@babel\/runtime|@babel\/runtime-corejs3|regenerator-runtime|ric-shim|babel-runtime)[\\/].*/, + name: 'polyfill', + chunks: 'all', + }, + rdc: { + test: /packages/, + name: 'rdc', + chunks: 'all', + priority: 10000, + }, + }, + }; + return config; +}; + +module.exports.options = options; diff --git a/package.json b/package.json index 769a7403a330..f9bc5ff15e64 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "workspaces": [ "packages/*", "examples/benchmark", + "examples/test-bundlesize", "examples/normalizr-*", "examples/coin-app", "website", @@ -23,6 +24,7 @@ "ci:build:legacy-types": "yarn workspaces foreach -WptivR -j 10 --from @data-client/react --from @data-client/rest --from @data-client/graphql run build:legacy-types", "ci:build-test-lib": "yarn workspace @data-client/test run build:lib && yarn workspace @data-client/test run build:bundle", "ci:build:esmodule": "yarn workspaces foreach -WptivR --from @data-client/react --from @data-client/rest --from @data-client/graphql run build:lib && yarn workspace @data-client/normalizr run build:js:node && yarn workspace @data-client/endpoint run build:js:node", + "ci:build:bundlesize": "yarn workspace test-bundlesize run build:sizecompare", "build:copy:ambient": "mkdirp ./packages/endpoint/lib && copyfiles --flat ./packages/endpoint/src/schema.d.ts ./packages/endpoint/lib/ && copyfiles --flat ./packages/endpoint/src/endpoint.d.ts ./packages/endpoint/lib/ && mkdirp ./packages/rest/lib && copyfiles --flat ./packages/rest/src/RestEndpoint.d.ts ./packages/rest/lib && copyfiles --flat ./packages/rest/src/next/RestEndpoint.d.ts ./packages/rest/lib/next && mkdirp ./packages/react/lib && copyfiles --flat ./packages/react/src/server/redux/redux.d.ts ./packages/react/lib/server/redux", "copy:websitetypes": "./scripts/copywebsitetypes.sh", "test": "NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules run jest", diff --git a/yarn.lock b/yarn.lock index 1e6308a2cbd8..d36b815d5ff2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28742,6 +28742,30 @@ __metadata: languageName: node linkType: hard +"test-bundlesize@workspace:examples/test-bundlesize": + version: 0.0.0-use.local + resolution: "test-bundlesize@workspace:examples/test-bundlesize" + dependencies: + "@anansi/babel-preset": "npm:6.2.1" + "@anansi/browserslist-config": "npm:^1.4.3" + "@anansi/webpack-config": "npm:^20.0.0" + "@babel/core": "npm:^7.22.15" + "@babel/runtime-corejs3": "npm:^7.26.7" + "@data-client/img": "npm:^0.14.0" + "@data-client/react": "npm:^0.14.0" + "@data-client/rest": "npm:^0.14.0" + "@types/react": "npm:*" + "@types/react-dom": "npm:*" + core-js: "npm:^3.40.0" + react: "npm:^19.0.0" + react-dom: "npm:^19.0.0" + react-refresh: "npm:*" + typescript: "npm:5.7.3" + webpack: "npm:*" + webpack-cli: "npm:*" + languageName: unknown + linkType: soft + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0"