diff --git a/.github/workflows/test-multiple-builds.yml b/.github/workflows/test-multiple-builds.yml new file mode 100644 index 0000000000..68c6ff2669 --- /dev/null +++ b/.github/workflows/test-multiple-builds.yml @@ -0,0 +1,57 @@ +name: Test Multiple Builds + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize] + +jobs: + test_matrix: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + build: [cjs, umd] # [cjs, esm, umd, system] + env: [development, production] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '12' + cache: yarn + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - run: yarn install --frozen-lockfile --check-files + - run: yarn build + - name: Patch for DEV-ONLY + if: ${{ matrix.env == 'development' }} + run: | + sed -i~ "s/it[a-zA-Z]*('\[PRD-ONLY\]/it.skip('/" tests/*.tsx + - name: Patch for PRD-ONLY + if: ${{ matrix.env == 'production' }} + run: | + sed -i~ "s/it[a-zA-Z]*('\[DEV-ONLY\]/it.skip('/" tests/*.tsx + - name: Patch for CJS + if: ${{ matrix.build == 'cjs' }} + run: | + sed -i~ "s/\/src\(.*\)\.ts/\/dist\1.js/" package.json + - name: Patch for ESM + if: ${{ matrix.build == 'esm' }} + run: | + sed -i~ "s/\/src\(.*\)\.ts/\/dist\/esm\1.js" package.json + sed -i~ "1s/^/import.meta.env=import.meta.env||{};import.meta.env.MODE='${NODE_ENV}';/" tests/*.tsx + env: + NODE_ENV: ${{ matrix.env }} + - name: Patch for UMD/SystemJS + if: ${{ matrix.build == 'umd' || matrix.build == 'system' }} + run: | + sed -i~ "s/\/src\(.*\)\.ts/\/dist\/${BUILD}\1.${NODE_ENV}.js/" package.json + env: + BUILD: ${{ matrix.build }} + NODE_ENV: ${{ matrix.env }} + - name: Test ${{ matrix.build }} ${{ matrix.env }} + run: | + cat package.json + yarn test:ci + env: + NODE_ENV: ${{ matrix.env }} diff --git a/package.json b/package.json index f8df402de4..128e2f8ac8 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "sideEffects": false, "scripts": { "prebuild": "shx rm -rf dist", - "build": "concurrently 'yarn:build:*'", + "build": "concurrently -m 8 'yarn:build:*'", "build:base": "rollup -c", "build:vanilla": "rollup -c --config-vanilla", "build:middleware": "rollup -c --config-middleware", @@ -106,6 +106,9 @@ "@swc/jest" ] }, + "globals": { + "__DEV__": true + }, "moduleNameMapper": { "^zustand$": "/src/index.ts", "^zustand/(.*)$": "/src/$1.ts" @@ -136,6 +139,7 @@ "@babel/preset-env": "^7.16.5", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-node-resolve": "^13.1.1", + "@rollup/plugin-replace": "^3.0.1", "@rollup/plugin-typescript": "^8.3.0", "@swc/core": "^1.2.122", "@swc/jest": "^0.2.15", @@ -165,6 +169,7 @@ "react-dom": "^17.0.2", "rollup": "^2.62.0", "rollup-plugin-esbuild": "^4.7.2", + "rollup-plugin-terser": "^7.0.2", "shx": "^0.3.3", "typescript": "^4.5.4" }, diff --git a/rollup.config.js b/rollup.config.js index ad7714fd1e..d480fa7cf1 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,8 +1,10 @@ import path from 'path' import babelPlugin from '@rollup/plugin-babel' import resolve from '@rollup/plugin-node-resolve' +import replace from '@rollup/plugin-replace' import typescript from '@rollup/plugin-typescript' import esbuild from 'rollup-plugin-esbuild' +import { terser } from 'rollup-plugin-terser' const createBabelConfig = require('./babel.config') const extensions = ['.js', '.ts', '.tsx'] @@ -21,9 +23,9 @@ function getBabelOptions(targets) { } } -function getEsbuild(target) { +function getEsbuild(target, env = 'development') { return esbuild({ - minify: false, + minify: env === 'production', target, tsconfig: path.resolve('./tsconfig.json'), }) @@ -54,49 +56,79 @@ function createESMConfig(input, output) { { file: `${output}.mjs`, format: 'esm' }, ], external, - plugins: [resolve({ extensions }), getEsbuild('node12')], + plugins: [ + resolve({ extensions }), + replace({ + __DEV__: '(import.meta.env&&import.meta.env.MODE)!=="production"', + preventAssignment: true, + }), + getEsbuild('node12'), + ], } } function createCommonJSConfig(input, output) { return { input, - output: { file: output, format: 'cjs', exports: 'named' }, + output: { file: `${output}.js`, format: 'cjs', exports: 'named' }, external, plugins: [ resolve({ extensions }), + replace({ + __DEV__: 'process.env.NODE_ENV!=="production"', + preventAssignment: true, + }), babelPlugin(getBabelOptions({ ie: 11 })), ], } } -function createUMDConfig(input, output) { +function createUMDConfig(input, output, env) { + const c = output.split('/').pop() return { input, output: { - file: output, + file: `${output}.${env}.js`, format: 'umd', exports: 'named', - name: 'zustand', + name: + c === 'index' + ? 'zustand' + : `zustand${c.slice(0, 1).toUpperCase()}${c.slice(1)}`, + globals: { + react: 'React', + }, }, external, plugins: [ resolve({ extensions }), + replace({ + __DEV__: env !== 'production' ? 'true' : 'false', + preventAssignment: true, + }), babelPlugin(getBabelOptions({ ie: 11 })), + ...(env === 'production' ? [terser()] : []), ], } } -function createSystemConfig(input, output) { +function createSystemConfig(input, output, env) { return { input, output: { - file: output, + file: `${output}.${env}.js`, format: 'system', exports: 'named', }, external, - plugins: [resolve({ extensions }), getEsbuild('node12')], + plugins: [ + resolve({ extensions }), + replace({ + __DEV__: env !== 'production' ? 'true' : 'false', + preventAssignment: true, + }), + getEsbuild('node12', env), + ], } } @@ -104,18 +136,16 @@ export default function (args) { let c = Object.keys(args).find((key) => key.startsWith('config-')) if (c) { c = c.slice('config-'.length).replace(/_/g, '/') - return [ - createCommonJSConfig(`src/${c}.ts`, `dist/${c}.js`), - createESMConfig(`src/${c}.ts`, `dist/esm/${c}`), - createUMDConfig(`src/${c}.ts`, `dist/umd/${c}.js`), - createSystemConfig(`src/${c}.ts`, `dist/system/${c}.js`), - ] + } else { + c = 'index' } return [ - createDeclarationConfig('src/index.ts', 'dist'), - createCommonJSConfig('src/index.ts', 'dist/index.js'), - createESMConfig('src/index.ts', 'dist/esm/index'), - createUMDConfig('src/index.ts', 'dist/umd/index.js'), - createSystemConfig('src/index.ts', 'dist/system/index.js'), + ...(c === 'index' ? [createDeclarationConfig(`src/${c}.ts`, 'dist')] : []), + createCommonJSConfig(`src/${c}.ts`, `dist/${c}`), + createESMConfig(`src/${c}.ts`, `dist/esm/${c}`), + createUMDConfig(`src/${c}.ts`, `dist/umd/${c}`, 'development'), + createUMDConfig(`src/${c}.ts`, `dist/umd/${c}`, 'production'), + createSystemConfig(`src/${c}.ts`, `dist/system/${c}`, 'development'), + createSystemConfig(`src/${c}.ts`, `dist/system/${c}`, 'production'), ] } diff --git a/src/middleware/devtools.ts b/src/middleware/devtools.ts index 27b891ce20..0245c24554 100644 --- a/src/middleware/devtools.ts +++ b/src/middleware/devtools.ts @@ -176,10 +176,7 @@ export function devtools< } if (!extensionConnector) { - if ( - process.env.NODE_ENV === 'development' && - typeof window !== 'undefined' - ) { + if (__DEV__ && typeof window !== 'undefined') { console.warn( '[zustand devtools middleware] Please install/enable Redux devtools extension' ) diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000000..4781b52c00 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-var +declare var __DEV__: boolean diff --git a/tests/devtools.test.tsx b/tests/devtools.test.tsx index d6e5cb3ae8..96db41cbbf 100644 --- a/tests/devtools.test.tsx +++ b/tests/devtools.test.tsx @@ -35,22 +35,25 @@ it('connects to the extension by passing the options and initializes', () => { }) describe('If there is no extension installed...', () => { + let savedDEV: boolean beforeAll(() => { + savedDEV = __DEV__ ;(window as any).__REDUX_DEVTOOLS_EXTENSION__ = undefined }) afterAll(() => { + __DEV__ = savedDEV ;(window as any).__REDUX_DEVTOOLS_EXTENSION__ = extensionConnector }) it('does not throw', () => { + __DEV__ = false expect(() => { create(devtools(() => ({ count: 0 }))) }).not.toThrow() }) - it('warns in dev env', () => { - const originalNodeEnv = process.env.NODE_ENV - process.env.NODE_ENV = 'development' + it('[DEV-ONLY] warns in dev env', () => { + __DEV__ = true const originalConsoleWarn = console.warn console.warn = jest.fn() @@ -59,11 +62,11 @@ describe('If there is no extension installed...', () => { '[zustand devtools middleware] Please install/enable Redux devtools extension' ) - process.env.NODE_ENV = originalNodeEnv console.warn = originalConsoleWarn }) - it('does not warn if not in dev env', () => { + it('[PRD-ONLY] does not warn if not in dev env', () => { + __DEV__ = false const consoleWarn = jest.spyOn(console, 'warn') create(devtools(() => ({ count: 0 }))) diff --git a/tests/middlewareTypes.test.tsx b/tests/middlewareTypes.test.tsx index 18b4b8dbba..bf0cf502fc 100644 --- a/tests/middlewareTypes.test.tsx +++ b/tests/middlewareTypes.test.tsx @@ -71,6 +71,14 @@ describe('counter state spec (no middleware)', () => { }) describe('counter state spec (single middleware)', () => { + let savedDEV: boolean + beforeEach(() => { + savedDEV = __DEV__ + }) + afterEach(() => { + __DEV__ = savedDEV + }) + it('immer', () => { const useStore = create( immer((set, get) => ({ @@ -118,6 +126,7 @@ describe('counter state spec (single middleware)', () => { }) it('devtools', () => { + __DEV__ = false const useStore = create< CounterState, SetState, @@ -243,7 +252,16 @@ describe('counter state spec (single middleware)', () => { }) describe('counter state spec (double middleware)', () => { + let savedDEV: boolean + beforeEach(() => { + savedDEV = __DEV__ + }) + afterEach(() => { + __DEV__ = savedDEV + }) + it('devtools & immer', () => { + __DEV__ = false const useStore = create< CounterState, SetState, @@ -275,6 +293,7 @@ describe('counter state spec (double middleware)', () => { }) it('devtools & redux', () => { + __DEV__ = false const useStore = create( devtools( redux<{ count: number }, { type: 'INC' }>( @@ -303,6 +322,7 @@ describe('counter state spec (double middleware)', () => { }) it('devtools & combine', () => { + __DEV__ = false const useStore = create( devtools( combine({ count: 1 }, (set, get) => ({ @@ -349,6 +369,7 @@ describe('counter state spec (double middleware)', () => { }) it('devtools & subscribeWithSelector', () => { + __DEV__ = false const useStore = create< CounterState, SetState, @@ -382,6 +403,7 @@ describe('counter state spec (double middleware)', () => { }) it('devtools & persist', () => { + __DEV__ = false const useStore = create< CounterState, SetState, @@ -415,7 +437,16 @@ describe('counter state spec (double middleware)', () => { }) describe('counter state spec (triple middleware)', () => { + let savedDEV: boolean + beforeEach(() => { + savedDEV = __DEV__ + }) + afterEach(() => { + __DEV__ = savedDEV + }) + it('devtools & persist & immer', () => { + __DEV__ = false const useStore = create< CounterState, SetState, @@ -451,6 +482,7 @@ describe('counter state spec (triple middleware)', () => { }) it('devtools & subscribeWithSelector & combine', () => { + __DEV__ = false const useStore = create( devtools( subscribeWithSelector( @@ -479,6 +511,7 @@ describe('counter state spec (triple middleware)', () => { }) it('devtools & subscribeWithSelector & persist', () => { + __DEV__ = false const useStore = create< CounterState, SetState, @@ -520,7 +553,16 @@ describe('counter state spec (triple middleware)', () => { }) describe('counter state spec (quadruple middleware)', () => { + let savedDEV: boolean + beforeEach(() => { + savedDEV = __DEV__ + }) + afterEach(() => { + __DEV__ = savedDEV + }) + it('devtools & subscribeWithSelector & persist & immer (#616)', () => { + __DEV__ = false const useStore = create< CounterState, SetState, diff --git a/yarn.lock b/yarn.lock index 06d242c7d4..a5b9322c66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1233,6 +1233,14 @@ is-module "^1.0.0" resolve "^1.19.0" +"@rollup/plugin-replace@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-3.0.1.tgz#f774550f482091719e52e9f14f67ffc0046a883d" + integrity sha512-989J5oRzf3mm0pO/0djTijdfEh9U3n63BIXN5X7T4U9BP+fN4oxQ6DvDuBvFaHA6scaHQRclqmKQEkBhB7k7Hg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + "@rollup/plugin-typescript@^8.3.0": version "8.3.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz#bc1077fa5897b980fc27e376c4e377882c63e68b" @@ -2064,6 +2072,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -3637,6 +3650,15 @@ jest-watcher@^27.4.2: jest-util "^27.4.2" string-length "^4.0.1" +jest-worker@^26.2.1: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + jest-worker@^27.4.5: version "27.4.5" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.5.tgz#d696e3e46ae0f24cff3fa7195ffba22889262242" @@ -3888,6 +3910,13 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= +magic-string@^0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -4279,6 +4308,13 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -4451,6 +4487,16 @@ rollup-plugin-esbuild@^4.7.2: joycon "^3.0.1" jsonc-parser "^3.0.0" +rollup-plugin-terser@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" + integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== + dependencies: + "@babel/code-frame" "^7.10.4" + jest-worker "^26.2.1" + serialize-javascript "^4.0.0" + terser "^5.0.0" + rollup@^2.62.0: version "2.62.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.62.0.tgz#9e640b419fc5b9e0241844f6d55258bd79986ecc" @@ -4479,6 +4525,11 @@ rxjs@^7.4.0: dependencies: tslib "~2.1.0" +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -4521,6 +4572,13 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -4600,7 +4658,7 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" -source-map-support@^0.5.6: +source-map-support@^0.5.6, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -4618,11 +4676,16 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: +source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + spawn-command@^0.0.2-1: version "0.0.2-1" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" @@ -4782,6 +4845,15 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" +terser@^5.0.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" + integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.20" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"