├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── fixture-package-preset-env ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── package.json └── src │ ├── .babelrc │ ├── browser.js │ ├── foo │ ├── a.js │ └── b.js │ ├── index.js │ └── node.js ├── fixture-package-ts ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── package.json ├── src │ ├── .babelrc │ ├── browser.ts │ ├── foo │ │ ├── a.ts │ │ └── b.ts │ ├── globals.d.ts │ ├── index.tsx │ └── node.ts └── tsconfig.json ├── fixture-package ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── package.json └── src │ ├── .babelrc │ ├── browser.js │ ├── foo │ ├── a.js │ └── b.js │ ├── index.js │ └── node.js ├── lerna.json ├── package.json ├── packages ├── babel-plugin-transform-cup-globals │ ├── README.md │ ├── index.js │ └── package.json ├── create-universal-package │ ├── README.md │ ├── bin │ │ ├── build.js │ │ ├── clean.js │ │ └── index.js │ ├── build.js │ ├── clean.js │ ├── package.json │ ├── preflight.js │ └── worker.js ├── eslint-config-cup-recommended │ ├── README.md │ ├── eslintrc.js │ └── package.json ├── eslint-config-cup │ ├── README.md │ ├── eslintrc.js │ └── package.json └── eslint-plugin-cup │ ├── README.md │ ├── index.js │ ├── lib │ └── rules │ │ └── no-undef.js │ ├── package.json │ └── test │ └── lib │ └── rules │ └── no-undef.js ├── test ├── e2e-preset-env.test.js ├── e2e-ts.test.js ├── e2e.test.js └── package.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint:recommended'], 3 | 4 | env: { 5 | node: true, 6 | es6: true, 7 | }, 8 | 9 | parserOptions: { 10 | ecmaVersion: 2018, 11 | }, 12 | 13 | plugins: ['eslint-plugin-prettier'], 14 | 15 | rules: { 16 | 'no-console': 'off', 17 | 'prettier/prettier': [ 18 | 'error', 19 | { 20 | useTabs: false, 21 | printWidth: 80, 22 | tabWidth: 2, 23 | singleQuote: true, 24 | trailingComma: 'all', 25 | bracketSpacing: false, 26 | jsxBracketSameLine: false, 27 | parser: 'babel', 28 | semi: true, 29 | }, 30 | ], 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /fixture-package 3 | /node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve/test/resolver/malformed_package_json/package.json 4 | 5 | [include] 6 | 7 | [libs] 8 | 9 | [lints] 10 | 11 | [options] 12 | 13 | [strict] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | lerna-debug.log 4 | yarn-error.log 5 | .nyc_output/ 6 | dist-*/ 7 | .idea 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "10.15.0" 5 | before_install: 6 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.13.0 7 | - export PATH=$HOME/.yarn/bin:$PATH 8 | cache: 9 | yarn: true 10 | script: 11 | - yarn run bootstrap 12 | - yarn run test 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Ryan Tsao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-universal-package 2 | 3 | [![build status][build-badge]][build-href] 4 | [![npm version][npm-badge]][npm-href] 5 | 6 | A toolchain for developing universal (Node.js and browser) JavaScript packages. 7 | 8 | ## Installation 9 | 10 | ``` 11 | npm i create-universal-package --save-dev 12 | ``` 13 | 14 | ## Usage 15 | 16 | ``` 17 | Usage: cup [options] [command] 18 | 19 | Commands: 20 | 21 | build, b Build your package 22 | build-tests Build your tests 23 | clean, c Clean build artifacts 24 | help Display help 25 | 26 | Options: 27 | 28 | -h, --help Output usage information 29 | -v, --version Output the version number 30 | ``` 31 | 32 | ### Tests 33 | 34 | Any `.js` files at the root of any `__tests__` directory will be added to the test bundle. For browser-only test files, you can use a `.browser.js` extension. This also works for node-only tests and `.node.js`. 35 | 36 | ### Globals 37 | 38 | ##### `__NODE__` and `__BROWSER__` 39 | 40 | Aliases for either `true` or `false` depending on the build target. Use this in conjunction with conditionals to check for environment, and dead code will automatically be eliminated appropriately. 41 | 42 | For linting purposes, `__BROWSER__` and/or `__NODE__` conditional checks establish appropriate environment globals. For example: 43 | 44 | ```js 45 | process.title; // fails `cup/no-undef` 46 | window.location; // fails `cup/no-undef` 47 | 48 | // passes lint 49 | if (__BROWSER__) { 50 | document.body.appendChild(document.createTextNode('hello world')); 51 | } 52 | 53 | // passes lint 54 | if (__NODE__) { 55 | process.stdout.write('hello world'); 56 | } 57 | 58 | // passes lint 59 | const topLevel = __BROWSER__ ? window : global; 60 | ``` 61 | 62 | By default, only universal globals (e.g. `setTimeout` and `console`) are set everywhere. 63 | 64 | ##### `__DEV__` 65 | 66 | Alias for `process.env.NODE_ENV !== 'production'`. By convention, it is assumed that module consumers are statically inlining the value of `process.env.NODE_ENV` in browser bundles. 67 | 68 | ### Dependencies 69 | 70 | create-universal-package prunes unused imports in scenarios like the following: 71 | 72 | ```js 73 | import doNodeThing from 'some-package'; 74 | 75 | export function foo() { 76 | console.log('foo'); 77 | if (__NODE__) { 78 | doNodeThing(); 79 | } 80 | } 81 | ``` 82 | 83 | ##### Node.js result 84 | 85 | ```js 86 | import doNodeThing from 'some-package'; 87 | 88 | export function foo() { 89 | console.log('foo'); 90 | doNodeThing(); 91 | } 92 | ``` 93 | 94 | ##### Browser result 95 | 96 | ```js 97 | export function foo() { 98 | console.log('foo'); 99 | } 100 | ``` 101 | 102 | Notice how the `some-package` import gets eliminated from the browser result. This is what we want, but keep in mind any dependencies that perform side effects when imported could be eliminated. 103 | 104 | [build-badge]: https://travis-ci.org/rtsao/create-universal-package.svg?branch=master 105 | [build-href]: https://travis-ci.org/rtsao/create-universal-package 106 | [npm-badge]: https://badge.fury.io/js/create-universal-package.svg 107 | [npm-href]: https://www.npmjs.com/package/create-universal-package 108 | -------------------------------------------------------------------------------- /fixture-package-preset-env/.eslintignore: -------------------------------------------------------------------------------- 1 | dist-*/ 2 | flow-typed/ 3 | -------------------------------------------------------------------------------- /fixture-package-preset-env/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:react/recommended', 4 | require.resolve('eslint-config-cup'), 5 | require.resolve('eslint-config-cup-recommended'), 6 | ], 7 | 8 | parser: 'babel-eslint', 9 | 10 | parserOptions: { 11 | ecmaVersion: 2018, 12 | 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | 18 | plugins: ['eslint-plugin-prettier'], 19 | 20 | rules: { 21 | 'prettier/prettier': [ 22 | 'error', 23 | { 24 | useTabs: false, 25 | printWidth: 80, 26 | tabWidth: 2, 27 | singleQuote: true, 28 | trailingComma: 'all', 29 | bracketSpacing: false, 30 | jsxBracketSameLine: false, 31 | parser: 'babel', 32 | semi: true, 33 | }, 34 | ], 35 | }, 36 | 37 | settings: { 38 | react: { 39 | version: 'detect', 40 | }, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /fixture-package-preset-env/.gitignore: -------------------------------------------------------------------------------- 1 | dist-*/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /fixture-package-preset-env/README.md: -------------------------------------------------------------------------------- 1 | # fixture-package 2 | 3 | An example package using create-universal-package 4 | -------------------------------------------------------------------------------- /fixture-package-preset-env/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cup/fixture-package-preset-env", 3 | "private": true, 4 | "version": "1.1.33", 5 | "description": "A fixture universal package", 6 | "author": "Ryan Tsao ", 7 | "homepage": "https://github.com/rtsao/create-universal-package", 8 | "repository": "git@github.com:rtsao/create-universal-package.git", 9 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 10 | "files": [ 11 | "dist-browser-esm", 12 | "dist-node-cjs", 13 | "dist-node-esm", 14 | "src" 15 | ], 16 | "main": "./dist-node-cjs/index.js", 17 | "module": "./dist-node-esm/index.js", 18 | "browser": { 19 | "./dist-node-cjs/index.js": "./dist-browser-cjs/index.js", 20 | "./dist-node-esm/index.js": "./dist-browser-esm/index.js" 21 | }, 22 | "scripts": { 23 | "clean": "cup clean", 24 | "build": "cup build", 25 | "lint": "eslint src/**", 26 | "prepublish": "npm run build" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.17.10", 30 | "@babel/preset-env": "^7.17.10", 31 | "@babel/preset-react": "^7.0.0", 32 | "babel-eslint": "^10.0.1", 33 | "create-universal-package": "^4.3.1", 34 | "enzyme": "^3.8.0", 35 | "enzyme-adapter-react-16": "^1.7.1", 36 | "eslint": "5.x", 37 | "eslint-config-cup": "^2.0.5", 38 | "eslint-config-cup-recommended": "^2.0.5", 39 | "eslint-plugin-cup": "^2.0.4", 40 | "eslint-plugin-prettier": "^3.0.1", 41 | "eslint-plugin-react": "^7.12.4", 42 | "nyc": "^13.1.0", 43 | "prettier": "^1.16.1", 44 | "react": "^16.7.0", 45 | "react-dom": "^16.7.0", 46 | "react-test-renderer": "^16.7.0" 47 | }, 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /fixture-package-preset-env/src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /fixture-package-preset-env/src/browser.js: -------------------------------------------------------------------------------- 1 | export const browser = 'browser'; 2 | -------------------------------------------------------------------------------- /fixture-package-preset-env/src/foo/a.js: -------------------------------------------------------------------------------- 1 | const a = 'a'; 2 | 3 | export default a; 4 | -------------------------------------------------------------------------------- /fixture-package-preset-env/src/foo/b.js: -------------------------------------------------------------------------------- 1 | const b = 'b'; 2 | 3 | export default b; 4 | -------------------------------------------------------------------------------- /fixture-package-preset-env/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-disable no-console */ 3 | 4 | import React from 'react'; 5 | 6 | import {browser} from './browser.js'; 7 | import {node} from './node.js'; 8 | 9 | if (__NODE__) { 10 | console.log(node); 11 | } 12 | 13 | if (__BROWSER__) { 14 | console.log(browser); 15 | } 16 | 17 | export function square(x: number) { 18 | return x * x; 19 | } 20 | 21 | export function Component() { 22 | return
Hello World
; 23 | } 24 | 25 | export function devIdentity(x) { 26 | if (__DEV__) { 27 | console.log(x); 28 | } 29 | return x; 30 | } 31 | -------------------------------------------------------------------------------- /fixture-package-preset-env/src/node.js: -------------------------------------------------------------------------------- 1 | export const node = 'node'; 2 | -------------------------------------------------------------------------------- /fixture-package-ts/.eslintignore: -------------------------------------------------------------------------------- 1 | dist-*/ 2 | -------------------------------------------------------------------------------- /fixture-package-ts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:react/recommended', 4 | require.resolve('eslint-config-cup'), 5 | require.resolve('eslint-config-cup-recommended'), 6 | ], 7 | 8 | parser: 'babel-eslint', 9 | 10 | parserOptions: { 11 | ecmaVersion: 2018, 12 | 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | 18 | plugins: ['eslint-plugin-prettier'], 19 | 20 | rules: { 21 | 'prettier/prettier': [ 22 | 'error', 23 | { 24 | useTabs: false, 25 | printWidth: 80, 26 | tabWidth: 2, 27 | singleQuote: true, 28 | trailingComma: 'all', 29 | bracketSpacing: false, 30 | jsxBracketSameLine: false, 31 | parser: 'babel', 32 | semi: true, 33 | }, 34 | ], 35 | }, 36 | 37 | settings: { 38 | react: { 39 | version: 'detect', 40 | }, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /fixture-package-ts/.gitignore: -------------------------------------------------------------------------------- 1 | dist-*/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /fixture-package-ts/README.md: -------------------------------------------------------------------------------- 1 | # fixture-package-ts 2 | 3 | An example package using create-universal-package with typescript 4 | -------------------------------------------------------------------------------- /fixture-package-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cup/fixture-package-ts", 3 | "private": true, 4 | "version": "1.1.30", 5 | "description": "A fixture universal package", 6 | "author": "Ryan Tsao ", 7 | "homepage": "https://github.com/rtsao/create-universal-package", 8 | "repository": "git@github.com:rtsao/create-universal-package.git", 9 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 10 | "files": [ 11 | "dist-browser-esm", 12 | "dist-node-cjs", 13 | "dist-node-esm", 14 | "src" 15 | ], 16 | "main": "./dist-node-cjs/index.js", 17 | "module": "./dist-node-esm/index.js", 18 | "browser": { 19 | "./dist-node-cjs/index.js": "./dist-browser-cjs/index.js", 20 | "./dist-node-esm/index.js": "./dist-browser-esm/index.js" 21 | }, 22 | "scripts": { 23 | "clean": "cup clean", 24 | "build": "cup build", 25 | "lint": "eslint src/**", 26 | "prepublish": "npm run build" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.10.5", 30 | "@babel/preset-react": "^7.10.4", 31 | "@types/react": "^16.7.0", 32 | "@types/react-dom": "^16.7.0", 33 | "babel-eslint": "^10.1.0", 34 | "create-universal-package": "^4.3.1", 35 | "eslint": "5.x", 36 | "eslint-config-cup": "^2.0.4", 37 | "eslint-config-cup-recommended": "^2.0.4", 38 | "eslint-plugin-cup": "^2.0.3", 39 | "eslint-plugin-prettier": "^3.0.1", 40 | "eslint-plugin-react": "^7.12.4", 41 | "prettier": "^1.16.1", 42 | "react": "^16.7.0", 43 | "react-dom": "^16.7.0", 44 | "react-test-renderer": "^16.7.0", 45 | "typescript": "^4.1.3" 46 | }, 47 | "license": "MIT" 48 | } 49 | -------------------------------------------------------------------------------- /fixture-package-ts/src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /fixture-package-ts/src/browser.ts: -------------------------------------------------------------------------------- 1 | export const browser = 'browser'; 2 | -------------------------------------------------------------------------------- /fixture-package-ts/src/foo/a.ts: -------------------------------------------------------------------------------- 1 | const a = 'a'; 2 | 3 | export default a; 4 | -------------------------------------------------------------------------------- /fixture-package-ts/src/foo/b.ts: -------------------------------------------------------------------------------- 1 | const b = 'b'; 2 | 3 | export default b; 4 | -------------------------------------------------------------------------------- /fixture-package-ts/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare var __NODE__: boolean; 2 | declare var __BROWSER__: boolean; 3 | declare var __DEV__: boolean; 4 | -------------------------------------------------------------------------------- /fixture-package-ts/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import React from "react"; 4 | 5 | import { browser } from "./browser"; 6 | import { node } from "./node"; 7 | 8 | if (__NODE__) { 9 | console.log(node); 10 | } 11 | 12 | if (__BROWSER__) { 13 | console.log(browser); 14 | } 15 | 16 | export function square(x: number) { 17 | return x * x; 18 | } 19 | 20 | export function Component() { 21 | return
Hello World
; 22 | } 23 | 24 | export function devIdentity(x:string) { 25 | if (__DEV__) { 26 | console.log(x); 27 | } 28 | return x; 29 | } 30 | -------------------------------------------------------------------------------- /fixture-package-ts/src/node.ts: -------------------------------------------------------------------------------- 1 | export const node = 'node'; 2 | -------------------------------------------------------------------------------- /fixture-package-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | }, 69 | "include": [ 70 | "./src" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /fixture-package/.eslintignore: -------------------------------------------------------------------------------- 1 | dist-*/ 2 | flow-typed/ 3 | -------------------------------------------------------------------------------- /fixture-package/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:react/recommended', 4 | require.resolve('eslint-config-cup'), 5 | require.resolve('eslint-config-cup-recommended'), 6 | ], 7 | 8 | parser: 'babel-eslint', 9 | 10 | parserOptions: { 11 | ecmaVersion: 2018, 12 | 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | 18 | plugins: ['eslint-plugin-prettier'], 19 | 20 | rules: { 21 | 'prettier/prettier': [ 22 | 'error', 23 | { 24 | useTabs: false, 25 | printWidth: 80, 26 | tabWidth: 2, 27 | singleQuote: true, 28 | trailingComma: 'all', 29 | bracketSpacing: false, 30 | jsxBracketSameLine: false, 31 | parser: 'babel', 32 | semi: true, 33 | }, 34 | ], 35 | }, 36 | 37 | settings: { 38 | react: { 39 | version: 'detect', 40 | }, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /fixture-package/.gitignore: -------------------------------------------------------------------------------- 1 | dist-*/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /fixture-package/README.md: -------------------------------------------------------------------------------- 1 | # fixture-package 2 | 3 | An example package using create-universal-package 4 | -------------------------------------------------------------------------------- /fixture-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cup/fixture-package", 3 | "private": true, 4 | "version": "1.1.33", 5 | "description": "A fixture universal package", 6 | "author": "Ryan Tsao ", 7 | "homepage": "https://github.com/rtsao/create-universal-package", 8 | "repository": "git@github.com:rtsao/create-universal-package.git", 9 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 10 | "files": [ 11 | "dist-browser-esm", 12 | "dist-node-cjs", 13 | "dist-node-esm", 14 | "src" 15 | ], 16 | "main": "./dist-node-cjs/index.js", 17 | "module": "./dist-node-esm/index.js", 18 | "browser": { 19 | "./dist-node-cjs/index.js": "./dist-browser-cjs/index.js", 20 | "./dist-node-esm/index.js": "./dist-browser-esm/index.js" 21 | }, 22 | "scripts": { 23 | "clean": "cup clean", 24 | "build": "cup build", 25 | "lint": "eslint src/**", 26 | "prepublish": "npm run build" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.17.10", 30 | "@babel/preset-react": "^7.16.0", 31 | "babel-eslint": "^10.1.0", 32 | "create-universal-package": "^4.3.1", 33 | "eslint": "5.x", 34 | "eslint-config-cup": "^2.0.5", 35 | "eslint-config-cup-recommended": "^2.0.5", 36 | "eslint-plugin-cup": "^2.0.4", 37 | "eslint-plugin-prettier": "^3.0.1", 38 | "eslint-plugin-react": "^7.12.4", 39 | "prettier": "^1.16.1", 40 | "react": "^16.7.0", 41 | "react-dom": "^16.7.0" 42 | }, 43 | "license": "MIT" 44 | } 45 | -------------------------------------------------------------------------------- /fixture-package/src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /fixture-package/src/browser.js: -------------------------------------------------------------------------------- 1 | export const browser = 'browser'; 2 | -------------------------------------------------------------------------------- /fixture-package/src/foo/a.js: -------------------------------------------------------------------------------- 1 | const a = 'a'; 2 | 3 | export default a; 4 | -------------------------------------------------------------------------------- /fixture-package/src/foo/b.js: -------------------------------------------------------------------------------- 1 | const b = 'b'; 2 | 3 | export default b; 4 | -------------------------------------------------------------------------------- /fixture-package/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-disable no-console */ 3 | 4 | import React from 'react'; 5 | 6 | import {browser} from './browser.js'; 7 | import {node} from './node.js'; 8 | 9 | if (__NODE__) { 10 | console.log(node); 11 | } 12 | 13 | if (__BROWSER__) { 14 | console.log(browser); 15 | } 16 | 17 | export function square(x: number) { 18 | return x * x; 19 | } 20 | 21 | export function Component() { 22 | return
Hello World
; 23 | } 24 | 25 | export function devIdentity(x) { 26 | if (__DEV__) { 27 | console.log(x); 28 | } 29 | return x; 30 | } 31 | -------------------------------------------------------------------------------- /fixture-package/src/node.js: -------------------------------------------------------------------------------- 1 | export const node = 'node'; 2 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0", 3 | "packages": [ 4 | "packages/*", 5 | "fixture-package", 6 | "fixture-package-preset-env", 7 | "test" 8 | ], 9 | "npmClient": "yarn", 10 | "useWorkspaces": true, 11 | "version": "independent" 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cup-monorepo", 3 | "private": true, 4 | "description": "Monorepo for create-universal-package", 5 | "author": "Ryan Tsao ", 6 | "homepage": "https://github.com/rtsao/create-universal-package", 7 | "repository": "git@github.com:rtsao/create-universal-package.git", 8 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 9 | "scripts": { 10 | "lint": "eslint --ignore-path .gitignore .", 11 | "bootstrap": "lerna bootstrap", 12 | "test": "lerna run test", 13 | "clean": "lerna clean" 14 | }, 15 | "dependencies": { 16 | "jest": "^23.6.0" 17 | }, 18 | "devDependencies": { 19 | "eslint": "^5.0.0", 20 | "eslint-plugin-prettier": "^3.0.1", 21 | "flow-bin": "^0.91.0", 22 | "lerna": "^3.10.7", 23 | "prettier": "^1.12.1" 24 | }, 25 | "workspaces": [ 26 | "packages/*", 27 | "fixture-package", 28 | "fixture-package-preset-env", 29 | "fixture-package-ts", 30 | "test" 31 | ], 32 | "engines": { 33 | "node": ">= 10.12.0" 34 | }, 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-cup-globals/README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-transform-cup-globals 2 | 3 | Babel plugin for transforming create-universal-package globals 4 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-cup-globals/index.js: -------------------------------------------------------------------------------- 1 | const targetMap = { 2 | __NODE__: 'node', 3 | __BROWSER__: 'browser', 4 | }; 5 | 6 | module.exports = babel => { 7 | const t = babel.types; 8 | 9 | const nodeEnv = t.memberExpression( 10 | t.memberExpression(t.identifier('process'), t.identifier('env')), 11 | t.identifier('NODE_ENV'), 12 | ); 13 | const nodeEnvCheck = t.binaryExpression( 14 | '!==', 15 | nodeEnv, 16 | t.stringLiteral('production'), 17 | ); 18 | 19 | return { 20 | visitor: { 21 | Identifier(path, state) { 22 | const {name} = path.node; 23 | if ( 24 | name !== '__DEV__' && 25 | name !== '__NODE__' && 26 | name !== '__BROWSER__' 27 | ) { 28 | return; 29 | } 30 | if (path.parent.type === 'MemberExpression') { 31 | return; 32 | } 33 | if (path.parent.type === 'ClassMethod') { 34 | return; 35 | } 36 | if (path.isPure()) { 37 | return; 38 | } 39 | if (name === '__DEV__') { 40 | path.replaceWith(nodeEnvCheck); 41 | } else { 42 | const target = state.opts.target; 43 | path.replaceWith(t.booleanLiteral(target === targetMap[name])); 44 | } 45 | }, 46 | }, 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-cup-globals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-cup-globals", 3 | "version": "1.0.2", 4 | "description": "Babel plugin for transforming create-universal-package globals", 5 | "author": "Ryan Tsao ", 6 | "homepage": "https://github.com/rtsao/create-universal-package", 7 | "repository": "git@github.com:rtsao/create-universal-package.git", 8 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 9 | "main": "index.js", 10 | "engines": { 11 | "node": ">= 6" 12 | }, 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /packages/create-universal-package/README.md: -------------------------------------------------------------------------------- 1 | # create-universal-package 2 | 3 | A CLI for developing universal (Node.js and browser) JavaScript packages. 4 | -------------------------------------------------------------------------------- /packages/create-universal-package/bin/build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const args = require('args'); 4 | args 5 | .option('dir', 'Path to package dir', process.cwd()) 6 | .option('skip-preflight', 'Skip preflight check', false) 7 | .option('force-flow', 'Force generation of flow libdef', false) 8 | .option('skip-flow', 'Skip generation of flow libdef', false) 9 | .option('watch', 'Watch files', false); 10 | 11 | const flags = args.parse(process.argv); 12 | 13 | require('../build.js')(flags).catch(err => { 14 | console.error(err); 15 | process.exitCode = 1; 16 | }); 17 | -------------------------------------------------------------------------------- /packages/create-universal-package/bin/clean.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const args = require('args'); 4 | args.option('dir', 'Path to package dir', process.cwd()); 5 | 6 | const flags = args.parse(process.argv); 7 | 8 | require('../clean.js')(flags).catch(err => { 9 | console.error(err); 10 | process.exitCode = 1; 11 | }); 12 | -------------------------------------------------------------------------------- /packages/create-universal-package/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const args = require('args'); 4 | args 5 | .command('build', 'Build your package', ['b']) 6 | .command('clean', 'Clean build artifacts', ['c']); 7 | 8 | args.parse(process.argv); 9 | -------------------------------------------------------------------------------- /packages/create-universal-package/build.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const Worker = require('jest-worker').default; 4 | const glob = require('tiny-glob'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | const {promisify} = require('util'); 8 | const writeFile = promisify(fs.writeFile); 9 | const mkdir = promisify(fs.mkdir); 10 | 11 | /*:: 12 | 13 | type BuildOpts = { 14 | dir: string, 15 | skipFlow: boolean, 16 | forceFlow: boolean, 17 | watch: boolean, 18 | skipPreflight: boolean, 19 | }; 20 | 21 | */ 22 | 23 | module.exports = async function build(argv /*: BuildOpts */) { 24 | const worker = new Worker(path.join(__dirname, 'worker.js')); 25 | worker.getStdout().pipe(process.stdout); 26 | worker.getStderr().pipe(process.stderr); 27 | 28 | if (!argv.skipPreflight) { 29 | const preflight = require('./preflight.js'); 30 | try { 31 | await preflight(path.join(argv.dir, 'package.json')); 32 | } catch (err) { 33 | worker.end(); 34 | throw err; 35 | } 36 | } 37 | 38 | const files = await glob('src/**/*.{js,ts,tsx}', { 39 | cwd: argv.dir, 40 | filesOnly: true, 41 | absolute: true, 42 | }); 43 | 44 | const result = Promise.all( 45 | files.map(filename => { 46 | return runBuild(worker, argv.dir, filename); 47 | }), 48 | ); 49 | 50 | if (argv.watch) { 51 | const chokidar = require('chokidar'); 52 | const watcher = chokidar.watch('src/**/*.js', { 53 | cwd: argv.dir, 54 | persistent: true, 55 | ignoreInitial: true, 56 | awaitWriteFinish: { 57 | stabilityThreshold: 50, 58 | pollInterval: 10, 59 | }, 60 | }); 61 | 62 | for (const type of ['add', 'change']) { 63 | watcher.on(type, filename => { 64 | runBuild(worker, argv.dir, filename); 65 | }); 66 | } 67 | } else { 68 | await result; 69 | worker.end(); 70 | } 71 | 72 | if (!argv.skipFlow && (argv.forceFlow || hasFlowConfig(argv.dir))) { 73 | await generateFlowLibdef(argv.dir); 74 | } 75 | }; 76 | 77 | async function runBuild(worker, root, filename) { 78 | try { 79 | await worker.buildFile(root, filename); 80 | console.log(`built ${path.relative(root, filename)}`); 81 | } catch (err) { 82 | console.error(err); 83 | process.exitCode = 1; 84 | } 85 | } 86 | 87 | function hasFlowConfig(dir) { 88 | const configPath = path.join(dir, '.flowconfig'); 89 | return fs.existsSync(configPath); 90 | } 91 | 92 | async function generateFlowLibdef(dir) { 93 | const fileContent = `// @flow 94 | 95 | export * from "../src/index.js"; 96 | `; 97 | 98 | const filepath = path.resolve(dir, 'dist-node-cjs/index.js.flow'); 99 | 100 | const dirname = path.dirname(filepath); 101 | 102 | if (!fs.existsSync(dirname)) { 103 | await mkdir(dirname, {recursive: true}); 104 | } 105 | return writeFile(filepath, fileContent); 106 | } 107 | -------------------------------------------------------------------------------- /packages/create-universal-package/clean.js: -------------------------------------------------------------------------------- 1 | const glob = require('tiny-glob'); 2 | const del = require('eliminate'); 3 | 4 | module.exports = async function clean(opts = {}) { 5 | const dirs = await glob('dist-*', {absolute: true, cwd: opts.dir}); 6 | return Promise.all(dirs.map(dirpath => del(dirpath))); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/create-universal-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-universal-package", 3 | "version": "4.3.1", 4 | "description": "A CLI for developing universal (Node.js and browser) JavaScript packages.", 5 | "author": "Ryan Tsao ", 6 | "repository": "rtsao/create-universal-package", 7 | "main": "index.js", 8 | "bin": { 9 | "cup": "./bin/index.js", 10 | "cup-build": "./bin/build.js", 11 | "cup-clean": "./bin/clean.js" 12 | }, 13 | "dependencies": { 14 | "@babel/core": "^7.17.10", 15 | "@babel/plugin-syntax-flow": "^7.16.0", 16 | "@babel/plugin-transform-flow-strip-types": "^7.16.0", 17 | "@babel/plugin-transform-modules-commonjs": "^7.17.9", 18 | "@babel/preset-typescript": "^7.16.7", 19 | "args": "^5.0.0", 20 | "babel-plugin-transform-cup-globals": "^1.0.2", 21 | "babel-plugin-transform-prune-unused-imports": "^1.0.1", 22 | "chokidar": "^3.4.3", 23 | "eliminate": "^1.0.2", 24 | "jest-worker": "^22.4.3", 25 | "tiny-glob": "^0.2.6" 26 | }, 27 | "engines": { 28 | "node": ">= 10.12.0" 29 | }, 30 | "license": "MIT" 31 | } 32 | -------------------------------------------------------------------------------- /packages/create-universal-package/preflight.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const fs = require('fs'); 4 | const {promisify} = require('util'); 5 | const assert = require('assert'); 6 | 7 | const readFile = promisify(fs.readFile); 8 | 9 | module.exports = async function preflight(filePath /*: string */) { 10 | const contents = await readFile(filePath, {encoding: 'utf8'}); 11 | const pkg = JSON.parse(contents); 12 | try { 13 | assertPkgField(pkg, 'main', './dist-node-cjs/index.js'); 14 | assertPkgField(pkg, 'module', './dist-node-esm/index.js'); 15 | assertPkgField(pkg, 'browser', { 16 | './dist-node-cjs/index.js': './dist-browser-cjs/index.js', 17 | './dist-node-esm/index.js': './dist-browser-esm/index.js', 18 | }); 19 | } catch (err) { 20 | throw err; 21 | } 22 | }; 23 | 24 | function assertPkgField(pkg, field, expected) { 25 | try { 26 | assert.deepEqual(pkg[field], expected); 27 | } catch (err) { 28 | throw new Error( 29 | `package.json "${field}" entry incorrect, expected value: ${JSON.stringify( 30 | expected, 31 | null, 32 | ' ', 33 | )}`, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/create-universal-package/worker.js: -------------------------------------------------------------------------------- 1 | const babel = require('@babel/core'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const {promisify} = require('util'); 5 | const mkdir = promisify(fs.mkdir); 6 | const writeFile = promisify(fs.writeFile); 7 | const readFile = promisify(fs.readFile); 8 | 9 | async function buildFile(root, filename) { 10 | const fileContents = readFile(filename, 'utf8'); 11 | const isTypescript = filename.endsWith('.ts') || filename.endsWith('.tsx'); 12 | 13 | const baseConfig = babel.loadPartialConfig({ 14 | cwd: root, 15 | filename, 16 | root, 17 | caller: { 18 | name: 'create-universal-package-worker', 19 | }, 20 | presets: isTypescript 21 | ? [['@babel/preset-typescript', {onlyRemoveTypeImports: true}]] 22 | : [], 23 | plugins: isTypescript 24 | ? [] 25 | : [ 26 | require.resolve('@babel/plugin-syntax-flow'), 27 | require.resolve('@babel/plugin-transform-flow-strip-types'), 28 | ], 29 | sourceMaps: 'inline', 30 | }).options; 31 | 32 | // If preset-env is an inherited preset, ensure that {modules: false} is set so we can generate 33 | // ESM modules. Babel ConfigItem objects are immutable so we need to clone the existing preset, 34 | // set the correct options, and replace it. 35 | const newPresets = []; 36 | baseConfig.presets.forEach(preset => { 37 | if (preset.file && preset.file.request.indexOf('@babel/preset-env') > -1) { 38 | // Clone the existing preset 39 | const configItem = babel.createConfigItem( 40 | [ 41 | preset.value, 42 | { 43 | ...(preset.options || {}), 44 | modules: false, 45 | }, 46 | '@babel/preset-env', 47 | ], 48 | { 49 | dirname: preset.dirname, 50 | type: 'preset', 51 | }, 52 | ); 53 | newPresets.push(configItem); 54 | } else { 55 | newPresets.push(preset); 56 | } 57 | }); 58 | baseConfig.presets = newPresets; 59 | 60 | const source = await fileContents; 61 | 62 | baseConfig.sourceFileName = path.relative( 63 | path.join(root, '__dist__'), 64 | filename 65 | ); 66 | 67 | const ast = babel.parseSync(source, baseConfig); 68 | 69 | const relative = path 70 | .relative(`${root}/src`, filename) 71 | .replace(/(js|ts|tsx)$/, 'js'); 72 | 73 | return Promise.all([ 74 | write( 75 | `${root}/dist-browser-cjs/${relative}`, 76 | build( 77 | ast, 78 | source, 79 | baseConfig, 80 | getPlugins({cjs: true, target: 'browser'}), 81 | ), 82 | ), 83 | write( 84 | `${root}/dist-browser-esm/${relative}`, 85 | build(ast, source, baseConfig, getPlugins({target: 'browser'})), 86 | ), 87 | write( 88 | `${root}/dist-node-cjs/${relative}`, 89 | build(ast, source, baseConfig, getPlugins({cjs: true, target: 'node'})), 90 | ), 91 | write( 92 | `${root}/dist-node-esm/${relative}`, 93 | build(ast, source, baseConfig, getPlugins({target: 'node'})), 94 | ), 95 | ]); 96 | } 97 | 98 | async function write(filename, contents) { 99 | try { 100 | await mkdir(path.dirname(filename), {recursive: true}); 101 | } catch (err) { 102 | if (err.code !== 'EEXIST') { 103 | throw err; 104 | } 105 | } 106 | return writeFile(filename, contents); 107 | } 108 | 109 | function build(ast, source, baseConfig, extraPlugins) { 110 | const {plugins, ...config} = baseConfig; 111 | const result = babel.transformFromAstSync(ast, source, { 112 | ...config, 113 | plugins: [...plugins, ...extraPlugins], 114 | }); 115 | return result.code; 116 | } 117 | 118 | const expressions = { 119 | node: { 120 | truthyExpressions: ['__NODE__'], 121 | falsyExpressions: ['__BROWSER__'], 122 | }, 123 | browser: { 124 | truthyExpressions: ['__BROWSER__'], 125 | falsyExpressions: ['__NODE__'], 126 | }, 127 | }; 128 | 129 | function getPlugins({cjs, target}) { 130 | const {falsyExpressions, truthyExpressions} = expressions[target]; 131 | const plugins = [ 132 | [require.resolve('babel-plugin-transform-cup-globals'), {target}], 133 | [ 134 | require.resolve('babel-plugin-transform-prune-unused-imports'), 135 | {falsyExpressions, truthyExpressions}, 136 | ], 137 | ]; 138 | 139 | if (cjs === true) { 140 | plugins.push(require.resolve('@babel/plugin-transform-modules-commonjs')); 141 | } 142 | 143 | return plugins; 144 | } 145 | 146 | exports.buildFile = buildFile; 147 | -------------------------------------------------------------------------------- /packages/eslint-config-cup-recommended/README.md: -------------------------------------------------------------------------------- 1 | # eslint-config-cup-recommended 2 | 3 | The recommended ESLint config for create-universal-package modules 4 | 5 | ## Rationale 6 | - Lint rules for libraries and modules should be different than applications 7 | - Lint rules for universal JavaScript should be different than Node.js or browser-specific JavaScript. 8 | 9 | Bring your own [prettier](https://github.com/prettier/prettier) config for code *style*. 10 | -------------------------------------------------------------------------------- /packages/eslint-config-cup-recommended/eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | ////////////////////////////////////////////////////////////////// 4 | // Possible errors 5 | // http://eslint.org/docs/rules/#possible-errors 6 | // Exhaustive list of rules as of Feb 13, 2017 7 | ////////////////////////////////////////////////////////////////// 8 | /** 9 | * TODO: decide if this rule should be on 10 | */ 11 | 'no-await-in-loop': 'off', 12 | /** 13 | * TODO: decide if this rule should be on 14 | */ 15 | 'no-cond-assign': 'off', 16 | /** 17 | * Prevent accidental console.log 18 | */ 19 | 'no-console': 'error', 20 | /** 21 | * TODO: decide if this rule should be on 22 | */ 23 | 'no-constant-condition': 'off', 24 | /** 25 | * TODO: decide if this rule should be on 26 | */ 27 | 'no-control-regex': 'off', 28 | /** 29 | * Prevent accidental debugger 30 | */ 31 | 'no-debugger': 'error', 32 | /** 33 | * There should never be duplicate parameters in function declaration/expressions 34 | */ 35 | 'no-dupe-args': 'error', 36 | /** 37 | * There should never be duplicate keys in object literals 38 | */ 39 | 'no-dupe-keys': 'error', 40 | /** 41 | * There should never be duplicate case labels 42 | */ 43 | 'no-duplicate-case': 'error', 44 | /** 45 | * There should never be empty regex character classes 46 | */ 47 | 'no-empty-character-class': 'error', 48 | /** 49 | * Disallow empty blocks except for catch clauses 50 | */ 51 | 'no-empty': ['error', {allowEmptyCatch: true}], 52 | /** 53 | * There should never be exception re-assignment in catch clauses 54 | */ 55 | 'no-ex-assign': 'error', 56 | /** 57 | * There should never be redundant boolean casting 58 | */ 59 | 'no-extra-boolean-cast': 'error', 60 | /** 61 | * Extra parens can make things more clear. 62 | * Furthermore, prettier will dictate this. 63 | */ 64 | 'no-extra-parens': 'off', 65 | /** 66 | * Prettier dictates this 67 | */ 68 | 'no-extra-semi': 'off', 69 | /** 70 | * Function declarations should never be re-assigned 71 | */ 72 | 'no-func-assign': 'error', 73 | /** 74 | * TODO: some discussion on this may be needed. 75 | * But this rule is obsolete in ES6, as block-scoped function declarations are ok 76 | */ 77 | 'no-inner-declarations': 'off', 78 | /** 79 | * There should never be invalid strings in RegExp constructors 80 | */ 81 | 'no-invalid-regexp': 'error', 82 | /** 83 | * There should never be irregular whitespace 84 | */ 85 | 'no-irregular-whitespace': 'error', 86 | /* 87 | * TODO: this is redundant in flow 88 | * Global object properties should never be called as functions 89 | */ 90 | 'no-obj-calls': 'error', 91 | /** 92 | * This rule is off because it can be annoying. 93 | * Calling built-in prototype methods is convenient. 94 | * We usually work with object literals so it's fine 95 | * to assume prototype methods exist 96 | */ 97 | 'no-prototype-builtins': 'off', 98 | /** 99 | * There should never be multiple spaces in regular expressions 100 | */ 101 | 'no-regex-spaces': 'error', 102 | /** 103 | * There should never be sparse arrays 104 | */ 105 | 'no-sparse-arrays': 'error', 106 | /** 107 | * This rule off because isn't very useful 108 | */ 109 | 'no-template-curly-in-string': 'off', 110 | /** 111 | * TODO: is this rule affected by semis and/or prettier? 112 | */ 113 | 'no-unexpected-multiline': 'error', 114 | /** 115 | * TODO: this rule is made reduntant by Flow 116 | */ 117 | 'no-unreachable': 'off', 118 | /** 119 | * There should never be flow control in finally blocks 120 | */ 121 | 'no-unsafe-finally': 'error', 122 | /** 123 | * TODO: decide on this rule 124 | */ 125 | 'no-unsafe-negation': 'off', 126 | /** 127 | * The isNaN function should always be used when checking for NaN 128 | */ 129 | 'use-isnan': 'error', 130 | /** 131 | * This rule is off because it's not useful. We don't use JSDoc. 132 | */ 133 | 'valid-jsdoc': 'off', 134 | /** 135 | * This protects against typos in string literals in conjuction with typeof 136 | */ 137 | 'valid-typeof': 'error', 138 | 139 | //////////////////////////////////////////////////////////////// 140 | // Best Practices 141 | // http://eslint.org/docs/rules/#best-practices 142 | // Note: this list is NOT exhaustive 143 | ////////////////////////////////////////////////////////////////// 144 | /** 145 | * Never allow empty destructuring 146 | */ 147 | 'no-empty-pattern': 'error', 148 | /** 149 | * Variables should never be re-declared 150 | */ 151 | 'no-redeclare': 'error', 152 | /** 153 | * There should never be redundant variable self-assignment 154 | */ 155 | 'no-self-assign': 'error', 156 | 157 | ////////////////////////////////////////////////////////////////// 158 | // Variables 159 | // http://eslint.org/docs/rules/#variables 160 | // Exhaustive list of rules as of Feb 15, 2017 161 | ////////////////////////////////////////////////////////////////// 162 | /** 163 | * Use 'env/no-undef-env' instead. 'no-undef' is also turned off by `eslint-config-cup` 164 | */ 165 | 'no-undef': 'off', 166 | /** 167 | * Variable shadowing should never be allowed 168 | */ 169 | 'no-shadow': 'error', 170 | /** 171 | * TODO: is this needed if no-shadow is on? 172 | * disallow identifiers from shadowing restricted names. For example, var undefined = 'foo'; 173 | * This can cause unexpected behavior, and makes code very difficult to read. 174 | */ 175 | 'no-shadow-restricted-names': 'error', 176 | /** 177 | * This is partially taken care of by let vs const, and it does not really matter. 178 | */ 179 | 'init-declarations': 'off', 180 | /** 181 | * disallow catch clause parameters from shadowing variables in the outer scope 182 | */ 183 | 'no-catch-shadow': 'error', 184 | /** 185 | * delete should only be used to delete properties from objects 186 | */ 187 | 'no-delete-var': 'error', 188 | /** 189 | * disallow labels that share a name with a variable. While this does not cause an error, 190 | * it leads to confusion as the take identifier refers to different things in different contexts 191 | */ 192 | 'no-label-var': 'error', 193 | /** 194 | * TODO: We may want to use this to dissallow certain globals in browser code. 195 | * For the base, we will turn it off as we don't have enough context as to what globals to turn on and off 196 | */ 197 | 'no-restricted-globals': 'off', 198 | /** 199 | * disallow initializing variables to undefined. Variables should be initialized with null instead. 200 | */ 201 | 'no-undef-init': 'error', 202 | /** 203 | * Use typeof "undefined" instead of === undefined, and never initialize something to undefined. 204 | */ 205 | 'no-undefined': 'error', 206 | /** 207 | * Unused variables can lead to unnecessary code execution or bundling. 208 | * In library code, unused function parameters is confusing for readers and may indicate incomplete 209 | * refactoring. 210 | */ 211 | 'no-unused-vars': ['error', {vars: 'all', args: 'after-used'}], 212 | /** 213 | * TODO: Do we want this turned on? Hoisting can be nice for organizing functions in a file. 214 | */ 215 | 'no-use-before-define': 'off', 216 | 217 | ////////////////////////////////////////////////////////////////// 218 | // ECMAScript 6 219 | // http://eslint.org/docs/rules/#ecmascript-6 220 | // Note: this list is NOT exhaustive 221 | ////////////////////////////////////////////////////////////////// 222 | /** 223 | * Never allow `const` declarations to be re-assigned 224 | */ 225 | 'no-const-assign': 'error', 226 | /** 227 | * Class declarations should never be re-assigned 228 | */ 229 | 'no-class-assign': 'error', 230 | /** 231 | * We should use const everywhere when possible 232 | */ 233 | 'prefer-const': 'error', 234 | /** 235 | * There should never be duplicate class members 236 | */ 237 | 'no-dupe-class-members': 'error', 238 | }, 239 | }; 240 | -------------------------------------------------------------------------------- /packages/eslint-config-cup-recommended/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-cup-recommended", 3 | "version": "2.0.5", 4 | "description": "The recommended ESLint config for create-universal-package", 5 | "author": "Ryan Tsao ", 6 | "homepage": "https://github.com/rtsao/create-universal-package", 7 | "repository": "git@github.com:rtsao/create-universal-package.git", 8 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 9 | "main": "eslintrc.js", 10 | "dependencies": { 11 | "eslint-config-cup": "^2.0.5", 12 | "eslint-plugin-prettier": "^2.6.0" 13 | }, 14 | "peerDependencies": { 15 | "eslint": "5.x - 6.x" 16 | }, 17 | "devDependencies": { 18 | "eslint": "^6.0.0" 19 | }, 20 | "engines": { 21 | "node": ">= 6" 22 | }, 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /packages/eslint-config-cup/README.md: -------------------------------------------------------------------------------- 1 | # eslint-config-cup 2 | 3 | The base ESLint config for create-universal-package 4 | -------------------------------------------------------------------------------- /packages/eslint-config-cup/eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | __NODE__: false, 4 | __BROWSER__: false, 5 | __DEV__: false, 6 | }, 7 | 8 | env: { 9 | /** 10 | * We only want globals shared by both node and browser by default 11 | * (setTimeout, console, etc.) 12 | * The rest will be controlled by env settings in the actual configs 13 | */ 14 | 'shared-node-browser': true, 15 | /** 16 | * Set ES6 globals (Map, Set, etc.) 17 | * Note: this also sets parserOptions: {ecmaVersion: 6} 18 | * (see https://github.com/eslint/eslint/blob/master/conf/environments.js) 19 | */ 20 | es6: true, 21 | }, 22 | 23 | plugins: ['eslint-plugin-cup'], 24 | 25 | rules: { 26 | 'no-undef': 'off', 27 | 'cup/no-undef': 'error', 28 | }, 29 | 30 | parserOptions: { 31 | /** 32 | * sourceType: 'module' allows for import/export 33 | */ 34 | sourceType: 'module', 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/eslint-config-cup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-cup", 3 | "version": "2.0.5", 4 | "description": "The base ESLint config for create-universal-package", 5 | "author": "Ryan Tsao ", 6 | "homepage": "https://github.com/rtsao/create-universal-package", 7 | "repository": "git@github.com:rtsao/create-universal-package.git", 8 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 9 | "main": "eslintrc.js", 10 | "peerDependencies": { 11 | "eslint": "5.x - 6.x", 12 | "eslint-plugin-cup": "^2.0.0" 13 | }, 14 | "devDependencies": { 15 | "eslint": "^6.0.0", 16 | "eslint-plugin-cup": "^2.0.4" 17 | }, 18 | "engines": { 19 | "node": ">= 6" 20 | }, 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /packages/eslint-plugin-cup/README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-cup 2 | 3 | ESLint rules for create-universal-package 4 | 5 | ## Rules 6 | 7 | ### `cup/no-undef` 8 | 9 | The same as the vanilla `no-undef`, but only allows environment-specific globals if guarded by environment checks. 10 | 11 | ```js 12 | process.title; // fails `cup/no-undef` 13 | window.location; // fails `cup/no-undef` 14 | 15 | // passes lint 16 | if (__BROWSER__) { 17 | document.body.appendChild(document.createTextNode('hello world')); 18 | } 19 | 20 | // passes lint 21 | if (__NODE__) { 22 | process.stdout.write('hello world'); 23 | } 24 | 25 | // passes lint 26 | const topLevel = __BROWSER__ ? window : global; 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/eslint-plugin-cup/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-undef': require('./lib/rules/no-undef'), 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/eslint-plugin-cup/lib/rules/no-undef.js: -------------------------------------------------------------------------------- 1 | const globals = require('globals'); 2 | 3 | const IS_BROWSER = '__BROWSER__'; 4 | const IS_NODEJS = '__NODE__'; 5 | 6 | module.exports = { 7 | meta: { 8 | schema: [ 9 | { 10 | type: 'object', 11 | properties: { 12 | typeof: { 13 | type: 'boolean', 14 | }, 15 | }, 16 | additionalProperties: false, 17 | }, 18 | ], 19 | }, 20 | 21 | create(context) { 22 | const options = context.options[0]; 23 | const considerTypeOf = (options && options.typeof === true) || false; 24 | 25 | return { 26 | 'Program:exit'() { 27 | const globalScope = context.getScope(); 28 | 29 | globalScope.through.forEach(ref => { 30 | const identifier = ref.identifier; 31 | if (!considerTypeOf && hasTypeOfOperator(identifier)) { 32 | return; 33 | } 34 | 35 | const env = lookupEnv(identifier); 36 | if (env === IS_BROWSER) { 37 | if (globals.browser.hasOwnProperty(identifier.name)) { 38 | return; 39 | } 40 | } else if (env === IS_NODEJS) { 41 | if (globals.node.hasOwnProperty(identifier.name)) { 42 | return; 43 | } 44 | } 45 | 46 | context.report({ 47 | node: identifier, 48 | message: "'{{name}}' is not defined.", 49 | data: identifier, 50 | }); 51 | }); 52 | }, 53 | }; 54 | }, 55 | }; 56 | 57 | function matchesEnv(id) { 58 | return id === IS_BROWSER || id === IS_NODEJS; 59 | } 60 | 61 | function inverseEnv(env) { 62 | return env === IS_BROWSER ? IS_NODEJS : IS_BROWSER; 63 | } 64 | 65 | // TODO: memoize 66 | function lookupEnv(node) { 67 | let parent = node.parent; 68 | while (parent) { 69 | if ( 70 | (parent.type === 'IfStatement' || 71 | parent.type === 'ConditionalExpression') && 72 | parent.test.type === 'Identifier' 73 | ) { 74 | if (matchesEnv(parent.test.name)) { 75 | return node === parent.consequent 76 | ? parent.test.name 77 | : inverseEnv(parent.test.name); 78 | } 79 | } 80 | node = parent; 81 | parent = parent.parent; 82 | } 83 | } 84 | 85 | function hasTypeOfOperator(node) { 86 | const parent = node.parent; 87 | return parent.type === 'UnaryExpression' && parent.operator === 'typeof'; 88 | } 89 | -------------------------------------------------------------------------------- /packages/eslint-plugin-cup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-cup", 3 | "version": "2.0.4", 4 | "description": "ESlint rules for create-universal-package", 5 | "keywords": [ 6 | "eslint", 7 | "eslintplugin" 8 | ], 9 | "author": "Ryan Tsao ", 10 | "homepage": "https://github.com/rtsao/create-universal-package", 11 | "repository": "git@github.com:rtsao/create-universal-package.git", 12 | "bugs": "https://github.com/rtsao/create-universal-package/issues", 13 | "main": "index.js", 14 | "scripts": { 15 | "test": "node test/lib/rules/no-undef.js" 16 | }, 17 | "dependencies": { 18 | "globals": "^11.5.0" 19 | }, 20 | "peerDependencies": { 21 | "eslint": "5.x - 6.x" 22 | }, 23 | "devDependencies": { 24 | "eslint": "^6.0.0" 25 | }, 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /packages/eslint-plugin-cup/test/lib/rules/no-undef.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../../lib/rules/no-undef'); 2 | const RuleTester = require('eslint').RuleTester; 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('no-undef', rule, { 7 | valid: [ 8 | // env stuff 9 | { 10 | code: 'function a(){}if (__BROWSER__) {a(window)}', 11 | globals: {__BROWSER__: false}, 12 | }, 13 | { 14 | code: 'function a(){}if (__NODE__) {a(__dirname)}', 15 | globals: {__NODE__: false}, 16 | }, 17 | { 18 | code: 'function a(){}if (__BROWSER__) {a(window)} else {a(process)}', 19 | globals: {__BROWSER__: false}, 20 | }, 21 | {code: 'var foo = __NODE__ ? process : window', globals: {__NODE__: false}}, 22 | 23 | 'var a = 1, b = 2; a;', 24 | '/*global b*/ function f() { b; }', 25 | {code: 'function f() { b; }', globals: {b: false}}, 26 | {code: 'function f() { b; }', globals: {b: false}}, 27 | '/*global b a:false*/ a; function f() { b; a; }', 28 | 'function a(){} a();', 29 | 'function f(b) { b; }', 30 | 'var a; a = 1; a++;', 31 | 'var a; function f() { a = 1; }', 32 | '/*global b:true*/ b++;', 33 | '/*eslint-env browser*/ window;', 34 | '/*eslint-env browser*/ window;', 35 | '/*eslint-env node*/ require("a");', 36 | 'Object; isNaN();', 37 | 'toString()', 38 | 'hasOwnProperty()', 39 | 'function evilEval(stuffToEval) { var ultimateAnswer; ultimateAnswer = 42; eval(stuffToEval); }', 40 | 'typeof a', 41 | 'typeof (a)', 42 | 'var b = typeof a', 43 | "typeof a === 'undefined'", 44 | "if (typeof a === 'undefined') {}", 45 | { 46 | code: 'function foo() { var [a, b=4] = [1, 2]; return {a, b}; }', 47 | parserOptions: {ecmaVersion: 6}, 48 | }, 49 | {code: 'var toString = 1;', parserOptions: {ecmaVersion: 6}}, 50 | { 51 | code: 'function myFunc(...foo) { return foo;}', 52 | parserOptions: {ecmaVersion: 6}, 53 | }, 54 | { 55 | code: 'var React, App, a=1; React.render();', 56 | parserOptions: {ecmaVersion: 6, ecmaFeatures: {jsx: true}}, 57 | }, 58 | { 59 | code: 'var console; [1,2,3].forEach(obj => {\n console.log(obj);\n});', 60 | parserOptions: {ecmaVersion: 6}, 61 | }, 62 | { 63 | code: 'var Foo; class Bar extends Foo { constructor() { super(); }}', 64 | parserOptions: {ecmaVersion: 6}, 65 | }, 66 | { 67 | code: 68 | "import Warning from '../lib/warning'; var warn = new Warning('text');", 69 | parserOptions: {ecmaVersion: 2015, sourceType: 'module'}, 70 | }, 71 | { 72 | code: 73 | "import * as Warning from '../lib/warning'; var warn = new Warning('text');", 74 | parserOptions: {ecmaVersion: 2015, sourceType: 'module'}, 75 | }, 76 | {code: 'var a; [a] = [0];', parserOptions: {ecmaVersion: 6}}, 77 | {code: 'var a; ({a} = {});', parserOptions: {ecmaVersion: 6}}, 78 | {code: 'var a; ({b: a} = {});', parserOptions: {ecmaVersion: 6}}, 79 | { 80 | code: 'var obj; [obj.a, obj.b] = [0, 1];', 81 | parserOptions: {ecmaVersion: 6}, 82 | }, 83 | {code: 'URLSearchParams;', env: {browser: true}}, 84 | {code: 'Intl;', env: {browser: true}}, 85 | {code: 'IntersectionObserver;', env: {browser: true}}, 86 | {code: 'Credential;', env: {browser: true}}, 87 | {code: 'requestIdleCallback;', env: {browser: true}}, 88 | {code: 'customElements;', env: {browser: true}}, 89 | {code: 'PromiseRejectionEvent;', env: {browser: true}}, 90 | 91 | // Notifications of readonly are removed: https://github.com/eslint/eslint/issues/4504 92 | {code: '/*global b:false*/ function f() { b = 1; }'}, 93 | {code: 'function f() { b = 1; }', globals: {b: false}}, 94 | {code: '/*global b:false*/ function f() { b++; }'}, 95 | {code: '/*global b*/ b = 1;'}, 96 | {code: '/*global b:false*/ var b = 1;'}, 97 | {code: 'Array = 1;'}, 98 | 99 | // new.target: https://github.com/eslint/eslint/issues/5420 100 | { 101 | code: 'class A { constructor() { new.target; } }', 102 | parserOptions: {ecmaVersion: 6}, 103 | }, 104 | 105 | // sourceType: 'module' 106 | { 107 | code: 'function a(){}if (__BROWSER__) {a(window)}', 108 | parserOptions: { 109 | ecmaVersion: 6, 110 | sourceType: 'module', 111 | }, 112 | globals: { 113 | __BROWSER__: false, 114 | }, 115 | }, 116 | 117 | // Experimental, 118 | { 119 | code: 'var {bacon, ...others} = stuff; foo(others)', 120 | parserOptions: { 121 | ecmaVersion: 2018, 122 | }, 123 | globals: {stuff: false, foo: false}, 124 | }, 125 | ], 126 | invalid: [ 127 | { 128 | code: 'a = 1;', 129 | errors: [{message: "'a' is not defined.", type: 'Identifier'}], 130 | }, 131 | { 132 | code: "if (typeof anUndefinedVar === 'string') {}", 133 | options: [{typeof: true}], 134 | errors: [ 135 | {message: "'anUndefinedVar' is not defined.", type: 'Identifier'}, 136 | ], 137 | }, 138 | { 139 | code: 'var a = b;', 140 | errors: [{message: "'b' is not defined.", type: 'Identifier'}], 141 | }, 142 | { 143 | code: 'function f() { b; }', 144 | errors: [{message: "'b' is not defined.", type: 'Identifier'}], 145 | }, 146 | { 147 | code: 'window;', 148 | errors: [{message: "'window' is not defined.", type: 'Identifier'}], 149 | }, 150 | { 151 | code: 'require("a");', 152 | errors: [{message: "'require' is not defined.", type: 'Identifier'}], 153 | }, 154 | { 155 | code: 'var React; React.render();', 156 | errors: [{message: "'a' is not defined."}], 157 | parserOptions: {ecmaVersion: 6, ecmaFeatures: {jsx: true}}, 158 | }, 159 | { 160 | code: 'var React, App; React.render();', 161 | errors: [{message: "'a' is not defined."}], 162 | parserOptions: {ecmaVersion: 6, ecmaFeatures: {jsx: true}}, 163 | }, 164 | { 165 | code: '[a] = [0];', 166 | parserOptions: {ecmaVersion: 6}, 167 | errors: [{message: "'a' is not defined."}], 168 | }, 169 | { 170 | code: '({a} = {});', 171 | parserOptions: {ecmaVersion: 6}, 172 | errors: [{message: "'a' is not defined."}], 173 | }, 174 | { 175 | code: '({b: a} = {});', 176 | parserOptions: {ecmaVersion: 6}, 177 | errors: [{message: "'a' is not defined."}], 178 | }, 179 | { 180 | code: '[obj.a, obj.b] = [0, 1];', 181 | parserOptions: {ecmaVersion: 6}, 182 | errors: [ 183 | {message: "'obj' is not defined."}, 184 | {message: "'obj' is not defined."}, 185 | ], 186 | }, 187 | 188 | // Experimental 189 | { 190 | code: 'const c = 0; const a = {...b, c};', 191 | parserOptions: { 192 | ecmaVersion: 2018, 193 | }, 194 | errors: [{message: "'b' is not defined."}], 195 | }, 196 | ], 197 | }); 198 | -------------------------------------------------------------------------------- /test/e2e-preset-env.test.js: -------------------------------------------------------------------------------- 1 | /* global test */ 2 | 3 | const t = require('assert'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const cp = require('child_process'); 7 | const {promisify} = require('util'); 8 | const glob = require('tiny-glob'); 9 | 10 | const execFile = promisify(cp.execFile); 11 | const readFile = promisify(fs.readFile); 12 | 13 | const fixture = path.resolve(__dirname, '../fixture-package-preset-env'); 14 | 15 | test('builds', async () => { 16 | try { 17 | await execFile('yarn', ['build'], {cwd: fixture}); 18 | } catch (err) { 19 | console.log(err); 20 | } 21 | 22 | const files = await glob('src/**/*.js', {cwd: fixture}); 23 | 24 | t.deepEqual(files, [ 25 | 'src/browser.js', 26 | 'src/foo/a.js', 27 | 'src/foo/b.js', 28 | 'src/index.js', 29 | 'src/node.js', 30 | ]); 31 | 32 | const relative = files.map(file => path.relative('src', file)); 33 | 34 | ['dist-browser-esm', 'dist-node-cjs', 'dist-browser-esm'].forEach(base => { 35 | const artifacts = relative.map(file => path.join(fixture, base, file)); 36 | 37 | artifacts.forEach(file => { 38 | t.equal(fs.existsSync(file), true); 39 | }); 40 | }); 41 | }); 42 | 43 | test('node esm build has imports and exports', async () => { 44 | for (const file of ['browser.js', 'index.js', 'node.js']) { 45 | const contents = await readFile(path.join(fixture, 'dist-node-esm', file)); 46 | // No commonjs transformations 47 | t.equal(contents.indexOf('__esModule'), -1); 48 | } 49 | }); 50 | 51 | test('browser esm build has imports and exports', async () => { 52 | for (const file of ['browser.js', 'index.js', 'node.js']) { 53 | const contents = await readFile( 54 | path.join(fixture, 'dist-browser-esm', file), 55 | ); 56 | // No commonjs transformations 57 | t.equal(contents.indexOf('__esModule'), -1); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /test/e2e-ts.test.js: -------------------------------------------------------------------------------- 1 | /* global test */ 2 | 3 | const t = require('assert'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const cp = require('child_process'); 7 | const {promisify} = require('util'); 8 | const babel = require('@babel/core'); 9 | const traverse = require('@babel/traverse').default; 10 | const glob = require('tiny-glob'); 11 | 12 | const execFile = promisify(cp.execFile); 13 | const readFile = filename => promisify(fs.readFile)(filename, 'utf-8'); 14 | 15 | const fixture = path.resolve(__dirname, '../fixture-package-ts'); 16 | 17 | test('builds', async () => { 18 | try { 19 | await execFile('yarn', ['build'], {cwd: fixture}); 20 | } catch (err) { 21 | console.log(err); 22 | } 23 | 24 | const files = await glob('src/**/*.{ts,tsx}', {cwd: fixture}); 25 | 26 | t.deepEqual(files, [ 27 | 'src/browser.ts', 28 | 'src/foo/a.ts', 29 | 'src/foo/b.ts', 30 | 'src/globals.d.ts', 31 | 'src/index.tsx', 32 | 'src/node.ts', 33 | ]); 34 | 35 | const relative = files.map(file => path.relative('src', file)); 36 | 37 | ['dist-browser-esm', 'dist-node-cjs', 'dist-browser-esm'].forEach(base => { 38 | const artifacts = relative.map(file => path.join(fixture, base, file)); 39 | 40 | artifacts.forEach(file => { 41 | t.equal(fs.existsSync(file.replace(/\.tsx?$/, '.js')), true); 42 | }); 43 | }); 44 | }); 45 | 46 | test('node build', async () => { 47 | const contents = await readFile(path.join(fixture, 'dist-node-esm/index.js')); 48 | 49 | const imports = extractImports(contents); 50 | t.deepEqual(imports, [ 51 | {specifiers: [{default: 'React'}], source: 'react'}, 52 | {specifiers: ['node'], source: './node'}, 53 | ]); 54 | }); 55 | 56 | test('browser build', async () => { 57 | const contents = await readFile( 58 | path.join(fixture, 'dist-browser-esm/index.js'), 59 | ); 60 | 61 | const imports = extractImports(contents); 62 | t.deepEqual(imports, [ 63 | {specifiers: [{default: 'React'}], source: 'react'}, 64 | {specifiers: ['browser'], source: './browser'}, 65 | ]); 66 | }); 67 | 68 | function extractImports(code) { 69 | const ast = babel.parse(code); 70 | const imports = []; 71 | traverse(ast, { 72 | ImportDeclaration(path) { 73 | imports.push({ 74 | specifiers: path.node.specifiers.map(specifierDescription), 75 | source: path.node.source.value, 76 | }); 77 | }, 78 | }); 79 | return imports; 80 | } 81 | 82 | function specifierDescription(node) { 83 | if (node.type === 'ImportDefaultSpecifier') { 84 | return {default: node.local.name}; 85 | } else { 86 | if (node.imported.name !== node.local.name) { 87 | return {name: node.imported.name, as: node.local.name}; 88 | } 89 | return node.local.name; 90 | } 91 | } 92 | 93 | test('non-zero status code if syntax error', async () => { 94 | const errorFilePath = path.join(fixture, 'src/syntax-error.js'); 95 | fs.writeFileSync(errorFilePath, 'let foo = %%%%;'); 96 | try { 97 | await execFile('yarn', ['build'], {cwd: fixture}); 98 | t.fail('Should error'); 99 | } catch (err) { 100 | t.ok(err.toString().includes('Unexpected token')); 101 | } finally { 102 | fs.unlinkSync(errorFilePath); 103 | } 104 | }); 105 | -------------------------------------------------------------------------------- /test/e2e.test.js: -------------------------------------------------------------------------------- 1 | /* global test */ 2 | 3 | const t = require('assert'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const cp = require('child_process'); 7 | const {promisify} = require('util'); 8 | const babel = require('@babel/core'); 9 | const traverse = require('@babel/traverse').default; 10 | const glob = require('tiny-glob'); 11 | 12 | const execFile = promisify(cp.execFile); 13 | const readFile = filename => promisify(fs.readFile)(filename, 'utf-8'); 14 | 15 | const fixture = path.resolve(__dirname, '../fixture-package'); 16 | 17 | test('builds', async () => { 18 | try { 19 | await execFile('yarn', ['build'], {cwd: fixture}); 20 | } catch (err) { 21 | console.log(err); 22 | } 23 | 24 | const files = await glob('src/**/*.js', {cwd: fixture}); 25 | 26 | t.deepEqual(files, [ 27 | 'src/browser.js', 28 | 'src/foo/a.js', 29 | 'src/foo/b.js', 30 | 'src/index.js', 31 | 'src/node.js', 32 | ]); 33 | 34 | const relative = files.map(file => path.relative('src', file)); 35 | 36 | ['dist-browser-esm', 'dist-node-cjs', 'dist-browser-esm'].forEach(base => { 37 | const artifacts = relative.map(file => path.join(fixture, base, file)); 38 | 39 | artifacts.forEach(file => { 40 | t.equal(fs.existsSync(file), true); 41 | }); 42 | }); 43 | }); 44 | 45 | test('node build', async () => { 46 | const contents = await readFile(path.join(fixture, 'dist-node-esm/index.js')); 47 | 48 | const imports = extractImports(contents); 49 | t.deepEqual(imports, [ 50 | {specifiers: [{default: 'React'}], source: 'react'}, 51 | {specifiers: ['node'], source: './node.js'}, 52 | ]); 53 | }); 54 | 55 | test('browser build', async () => { 56 | const contents = await readFile( 57 | path.join(fixture, 'dist-browser-esm/index.js'), 58 | ); 59 | 60 | const imports = extractImports(contents); 61 | t.deepEqual(imports, [ 62 | {specifiers: [{default: 'React'}], source: 'react'}, 63 | {specifiers: ['browser'], source: './browser.js'}, 64 | ]); 65 | }); 66 | 67 | function extractImports(code) { 68 | const ast = babel.parse(code); 69 | const imports = []; 70 | traverse(ast, { 71 | ImportDeclaration(path) { 72 | imports.push({ 73 | specifiers: path.node.specifiers.map(specifierDescription), 74 | source: path.node.source.value, 75 | }); 76 | }, 77 | }); 78 | return imports; 79 | } 80 | 81 | function specifierDescription(node) { 82 | if (node.type === 'ImportDefaultSpecifier') { 83 | return {default: node.local.name}; 84 | } else { 85 | if (node.imported.name !== node.local.name) { 86 | return {name: node.imported.name, as: node.local.name}; 87 | } 88 | return node.local.name; 89 | } 90 | } 91 | 92 | test('non-zero status code if syntax error', async () => { 93 | const errorFilePath = path.join(fixture, 'src/syntax-error.js'); 94 | fs.writeFileSync(errorFilePath, 'let foo = %%%%;'); 95 | try { 96 | await execFile('yarn', ['build'], {cwd: fixture}); 97 | t.fail('Should error'); 98 | } catch (err) { 99 | t.ok(err.toString().includes('Unexpected token')); 100 | } finally { 101 | fs.unlinkSync(errorFilePath); 102 | } 103 | }); 104 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.4", 4 | "private": true, 5 | "scripts": { 6 | "test": "jest" 7 | }, 8 | "dependencies": { 9 | "@babel/core": "^7.17.10", 10 | "@babel/traverse": "^7.17.10", 11 | "jest": "^23.6.0", 12 | "tiny-glob": "^0.2.6" 13 | } 14 | } 15 | --------------------------------------------------------------------------------