├── .changeset └── config.json ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel-plugin-transform-define-Hero.png ├── lib └── index.js ├── package.json ├── test ├── 0 │ ├── actual.js │ └── expected.js ├── .eslintrc ├── binding │ ├── actual.js │ └── expected.js ├── emptyString │ ├── actual.js │ └── expected.js ├── false │ ├── actual.js │ └── expected.js ├── identifier │ ├── actual.js │ └── expected.js ├── import-identifiers │ ├── actual.js │ └── expected.js ├── index.js ├── load-dynamic-babelrc │ ├── .babelrc.js │ ├── actual.js │ └── expected.js ├── member-expression │ ├── actual.js │ └── expected.js ├── null │ ├── actual.js │ └── expected.js ├── object-keys-properties │ ├── actual.js │ └── expected.js ├── unary-expression │ ├── actual.js │ └── expected.js └── undefined │ ├── actual.js │ └── expected.js └── yarn.lock /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": [ 4 | "@svitejs/changesets-changelog-github-compact", 5 | { 6 | "repo": "FormidableLabs/babel-plugin-transform-define" 7 | } 8 | ], 9 | "access": "public", 10 | "baseBranch": "master" 11 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/binding 3 | test/import-identifiers 4 | test/object-keys-properties 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - formidable/configurations/es6-node 4 | 5 | rules: 6 | max-len: 7 | - error 8 | - 120 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | node-version: [20.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: "yarn" 27 | 28 | - name: Use node_modules cache 29 | id: node-modules-cache 30 | uses: actions/cache@v4 31 | with: 32 | path: node_modules 33 | key: node-modules-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('./yarn.lock') }} 34 | restore-keys: | 35 | node-modules-${{ runner.os }}-${{ matrix.node-version }}- 36 | node-modules-${{ runner.os }}- 37 | 38 | - name: Project installation 39 | if: steps.node-modules-cache.outputs.cache-hit != 'true' 40 | run: yarn install --prefer-offline --frozen-lockfile --non-interactive 41 | env: 42 | CI: true 43 | 44 | - name: Checks 45 | run: yarn run check 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | id-token: write 13 | issues: write 14 | repository-projects: write 15 | deployments: write 16 | packages: write 17 | pull-requests: write 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: 20 23 | 24 | - name: Install dependencies 25 | run: yarn install --frozen-lockfile 26 | 27 | - name: Unit Tests 28 | run: yarn test 29 | 30 | - name: PR or Publish 31 | id: changesets 32 | uses: changesets/action@v1 33 | with: 34 | version: yarn changeset version 35 | publish: yarn changeset publish 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | .DS_Store 4 | .project 5 | .vscode 6 | node_modules 7 | npm-debug.log* 8 | yarn-error.log* 9 | package-lock.json 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/lib 3 | !LICENSE 4 | !CHANGELOG.md 5 | !README.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.1.4 4 | 5 | ### Patch Changes 6 | 7 | - follow-up to last release, also prevent object properties from being replaced ([#88](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/88)) 8 | 9 | ## 2.1.3 10 | 11 | ### Patch Changes 12 | 13 | - Addresses #85, avoids replacing object keys. ([#86](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/86)) 14 | 15 | ## 2.1.2 16 | 17 | ### Patch Changes 18 | 19 | - Adding GitHub release workflow ([#83](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/83)) 20 | 21 | ## 2.1.1 22 | 23 | - Avoid replacements for variable bindings via [#82](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/82). 24 | 25 | ## 2.1.0 26 | 27 | - Add memoization to `getSortedObjectPaths` utility for better performance under certain circumstances. 28 | 29 | ## 2.0.1 30 | 31 | - _Bug_: Support ES module identifiers [#69](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/69), [#70](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/70) (_[@jdlm-stripe][]_) 32 | 33 | ## 2.0.0 (2019-10-23) 34 | 35 | #### Breaking Changes 36 | 37 | - Change plugin options to **only** be a real JS object. Removes string configuration path option as now this is all possible with dynamic `.babelrc.js` or `babel.config.js` files. 38 | - Update to `@babel/core` / Babel 7+. 39 | - Update `package.json:engines` to minimum Node 8. 40 | 41 | #### Internal 42 | 43 | - Lint all `test` code. 44 | 45 | ## 1.3.2 (2019-10-22) 46 | 47 | - Various infrastructure updates [#54](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/54) 48 | - Add `CONTRIBUTING.md`, `yarn.lock`, `.npmignore`, update `.gitignore`. 49 | - Switch to `yarn` for workflows and `npm version` for release workflow. 50 | - _Bug_: Fix sort comparator [#48](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/48) 51 | - Add test for CallExpression identifiers [#35](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/35) 52 | - Fixed Markdown headings [#42](https://github.com/FormidableLabs/babel-plugin-transform-define/pull/42) 53 | 54 | ## 1.3.1 (2019-01-01) 55 | 56 | - Update lodash to fix vulnerabilities 57 | 58 | ## 1.3.0 (2017-05-15) 59 | 60 | #### User Facing Changes 61 | 62 | - Support falsy replacement values [https://github.com/FormidableLabs/babel-plugin-transform-define/pull/33] 63 | 64 | #### Internal 65 | 66 | - Update eslint config and packages 67 | - Update lodash version 68 | 69 | ## 1.2.0 (2016-08-25) 70 | 71 | #### User Facing Changes 72 | 73 | - Add the ability define config as a deep object 74 | - Add a Code of Conduct 75 | 76 | #### Internal 77 | 78 | - Remove release scripts 79 | - Rename `./modules` to `./src` 80 | - Add keywords and contributors to `package.json` 81 | - Remove author from `package.json` in favor of contributors 82 | - Expand test coverage 83 | 84 | ## 1.1.0 (2016-08-19) 85 | 86 | #### User Facing Changes 87 | 88 | - Add the ability to get config from a file 89 | - Add support for Identifiers 90 | - Major improvements to the README.md 91 | 92 | #### Internal 93 | 94 | - Add tests 95 | - Add lint 96 | - Add JSDoc 97 | - Add CI 98 | - Add explicit LICENSE file 99 | - Move dependencies into devDependencies 100 | - Major refactors to DRY the code 101 | 102 | ## 1.0.0 (2016-03-21) 103 | 104 | #### User Facing Changes 105 | 106 | - Update README.md to reference Webpack's Define Plugin 107 | 108 | ## 1.0.0 (2016-03-21) 109 | 110 | Initial Release 111 | 112 | [@jdlm-stripe]: https://github.com/jdlm-stripe 113 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | 3 | Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | Using welcoming and inclusive language 12 | Being respectful of differing viewpoints and experiences 13 | Gracefully accepting constructive criticism 14 | Focusing on what is best for the community 15 | Showing empathy towards other community members 16 | Examples of unacceptable behavior by participants include: 17 | 18 | The use of sexualized language or imagery and unwelcome sexual attention or advances 19 | Trolling, insulting/derogatory comments, and personal or political attacks 20 | Public or private harassment 21 | Publishing others' private information, such as a physical or electronic address, without explicit permission 22 | Other conduct which could reasonably be considered inappropriate in a professional setting 23 | Our Responsibilities 24 | 25 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 26 | 27 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 28 | 29 | Scope 30 | 31 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 32 | 33 | Enforcement 34 | 35 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 36 | 37 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 38 | 39 | Attribution 40 | 41 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4. 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for contributing! 4 | 5 | ## Development 6 | 7 | ```sh 8 | $ yarn lint 9 | $ yarn test 10 | 11 | # ... or all together ... 12 | $ yarn run check 13 | ``` 14 | 15 | ### Using changesets 16 | 17 | Our official release path is to use automation to perform the actual publishing of our packages. The steps are to: 18 | 19 | 1. A human developer adds a changeset. Ideally this is as a part of a PR that will have a version impact on a package. 20 | 2. On merge of a PR our automation system opens a "Version Packages" PR. 21 | 3. On merging the "Version Packages" PR, the automation system publishes the packages. 22 | 23 | Here are more details: 24 | 25 | ### Add a changeset 26 | 27 | When you would like to add a changeset (which creates a file indicating the type of change), in your branch/PR issue this command: 28 | 29 | ```sh 30 | $ yarn changeset 31 | ``` 32 | 33 | to produce an interactive menu. Navigate the packages with arrow keys and hit `` to select 1+ packages. Hit `` when done. Select semver versions for packages and add appropriate messages. From there, you'll be prompted to enter a summary of the change. Some tips for this summary: 34 | 35 | 1. Aim for a single line, 1+ sentences as appropriate. 36 | 2. Include issue links in GH format (e.g. `#123`). 37 | 3. You don't need to reference the current pull request or whatnot, as that will be added later automatically. 38 | 39 | After this, you'll see a new uncommitted file in `.changesets` like: 40 | 41 | ```sh 42 | $ git status 43 | # .... 44 | Untracked files: 45 | (use "git add ..." to include in what will be committed) 46 | .changeset/flimsy-pandas-marry.md 47 | ``` 48 | 49 | Review the file, make any necessary adjustments, and commit it to source. When we eventually do a package release, the changeset notes and version will be incorporated! 50 | 51 | ### Creating versions 52 | 53 | On a merge of a feature PR, the changesets GitHub action will open a new PR titled `"Version Packages"`. This PR is automatically kept up to date with additional PRs with changesets. So, if you're not ready to publish yet, just keep merging feature PRs and then merge the version packages PR later. 54 | 55 | ### Publishing packages 56 | 57 | On the merge of a version packages PR, the changesets GitHub action will publish the packages to npm. 58 | 59 | ### Manually Releasing a new version to NPM 60 | 61 |
62 | 63 | Only for project administrators 64 | 65 | 66 | 1. Update `CHANGELOG.md`, following format for previous versions 67 | 2. Commit as "Changes for version VERSION" 68 | 3. Run `npm version patch` (or `minor|major|VERSION`) to run tests and lint, 69 | build published directories, then update `package.json` + add a git tag. 70 | 4. Run `npm publish` and publish to NPM if all is well. 71 | 5. Run `git push && git push --tags` 72 | 73 |
74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Formidable 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Babel Plugin Transform Define — Formidable, We build the modern web 3 | 4 | 5 |

6 | 7 | 8 | 9 | 10 | npm version 11 | 12 | 13 | 14 | 15 | 16 | Maintenance Status 17 | 18 |

19 | 20 |

21 | Compile time code replacement for babel similar to Webpack's DefinePlugin 22 |

23 | 24 | *** 25 | 26 | ## Quick Start 27 | 28 | ```shell 29 | $ npm install --save-dev babel-plugin-transform-define 30 | ``` 31 | 32 | **.babelrc** 33 | 34 | ```json 35 | { 36 | "plugins": [ 37 | ["transform-define", { 38 | "process.env.NODE_ENV": "production", 39 | "typeof window": "object" 40 | }] 41 | ] 42 | } 43 | ``` 44 | 45 | **.babelrc.js** 46 | 47 | ```js 48 | // E.g., any dynamic logic with JS, environment variables, etc. 49 | const overrides = require("./another-path.js"); 50 | 51 | module.exports = { 52 | plugins: [ 53 | ["transform-define", { 54 | "process.env.NODE_ENV": "production", 55 | "typeof window": "object", 56 | ...overrides 57 | }] 58 | ] 59 | }; 60 | ``` 61 | 62 | ## Reference Documentation 63 | 64 | `babel-plugin-transform-define` can transform certain types of code as a babel transformation. 65 | 66 | ##### `Identifiers` 67 | 68 | *.babelrc* 69 | ```json 70 | { 71 | "plugins": [ 72 | ["transform-define", { 73 | "VERSION": "1.0.0", 74 | }] 75 | ] 76 | } 77 | ``` 78 | 79 | *Source Code* 80 | ```js 81 | VERSION; 82 | 83 | window.__MY_COMPANY__ = { 84 | version: VERSION 85 | }; 86 | ``` 87 | 88 | *Output Code* 89 | ```js 90 | "1.0.0"; 91 | 92 | window.__MY_COMPANY__ = { 93 | version: "1.0.0" 94 | }; 95 | ``` 96 | *** 97 | ##### `Member Expressions` 98 | 99 | *.babelrc* 100 | ```json 101 | { 102 | "plugins": [ 103 | ["transform-define", { 104 | "process.env.NODE_ENV": "production" 105 | }] 106 | ] 107 | } 108 | ``` 109 | 110 | *Source Code* 111 | ```js 112 | if (process.env.NODE_ENV === "production") { 113 | console.log(true); 114 | } 115 | ``` 116 | 117 | *Output Code* 118 | ```js 119 | if (true) { 120 | console.log(true); 121 | } 122 | ``` 123 | *** 124 | ##### `Unary Expressions` 125 | 126 | *.babelrc* 127 | ```json 128 | { 129 | "plugins": [ 130 | ["transform-define", { 131 | "typeof window": "object" 132 | }] 133 | ] 134 | } 135 | ``` 136 | 137 | *Source Code* 138 | ```js 139 | typeof window; 140 | typeof window === "object"; 141 | ``` 142 | 143 | *Output Code* 144 | ```js 145 | 'object'; 146 | true; 147 | ``` 148 | 149 | 150 | *** 151 | 152 | ## License 153 | 154 | [MIT License](http://opensource.org/licenses/MIT) 155 | 156 | 157 | ## Maintenance Status 158 | 159 | **Stable:** Formidable is not planning to develop any new features for this project. We are still responding to bug reports and security concerns. We are still welcoming PRs for this project, but PRs that include new features should be small and easy to integrate and should not include breaking changes. 160 | -------------------------------------------------------------------------------- /babel-plugin-transform-define-Hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/babel-plugin-transform-define/89e7e39e153b38a3befe6687b4406550ae2ecd26/babel-plugin-transform-define-Hero.png -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const traverse = require("traverse"); 4 | const { get, has, find, memoize } = require("lodash"); 5 | 6 | /** 7 | * Return an Array of every possible non-cyclic path in the object as a dot separated string sorted 8 | * by length. 9 | * 10 | * Example: 11 | * getSortedObjectPaths({ process: { env: { NODE_ENV: "development" } } }); 12 | * // => [ "process.env.NODE_ENV", "process.env" "process" ] 13 | * 14 | * @param {Object} obj A plain JavaScript Object 15 | * @return {Array} Sorted list of non-cyclic paths into obj 16 | */ 17 | const getSortedObjectPaths = memoize((obj) => { 18 | if (!obj) { return []; } 19 | 20 | return traverse(obj) 21 | .paths() 22 | .filter((arr) => arr.length) 23 | .map((arr) => arr.join(".")) 24 | .sort((a, b) => b.length - a.length); 25 | }); 26 | 27 | /** 28 | * Replace a node with a given value. If the replacement results in a BinaryExpression, it will be 29 | * evaluated. For example, if the result of the replacement is `var x = "production" === "production"` 30 | * The evaluation will make a second replacement resulting in `var x = true` 31 | * @param {function} replaceFn The function used to replace the node 32 | * @param {babelNode} nodePath The node to evaluate 33 | * @param {*} replacement The value the node will be replaced with 34 | * @return {undefined} 35 | */ 36 | const replaceAndEvaluateNode = (replaceFn, nodePath, replacement) => { 37 | nodePath.replaceWith(replaceFn(replacement)); 38 | 39 | if (nodePath.parentPath.isBinaryExpression()) { 40 | const result = nodePath.parentPath.evaluate(); 41 | 42 | if (result.confident) { 43 | nodePath.parentPath.replaceWith(replaceFn(result.value)); 44 | } 45 | } 46 | }; 47 | 48 | /** 49 | * Finds the first replacement in sorted object paths for replacements that causes comparator 50 | * to return true. If one is found, replaces the node with it. 51 | * @param {Object} replacements The object to search for replacements 52 | * @param {babelNode} nodePath The node to evaluate 53 | * @param {function} replaceFn The function used to replace the node 54 | * @param {function} comparator The function used to evaluate whether a node matches a value in `replacements` 55 | * @return {undefined} 56 | */ 57 | // eslint-disable-next-line max-params 58 | const processNode = (replacements, nodePath, replaceFn, comparator) => { 59 | const replacementKey = find(getSortedObjectPaths(replacements), 60 | (value) => comparator(nodePath, value)); 61 | if (has(replacements, replacementKey)) { 62 | replaceAndEvaluateNode(replaceFn, nodePath, get(replacements, replacementKey)); 63 | } 64 | }; 65 | 66 | /** 67 | * Checks if the given identifier is an ES module import 68 | * @param {babelNode} identifierNodePath The node to check 69 | * @return {boolean} Indicates if the provided node is an import specifier or references one 70 | */ 71 | const isImportIdentifier = (identifierNodePath) => { 72 | const containerType = get(identifierNodePath, ["container", "type"]); 73 | 74 | return containerType === "ImportDefaultSpecifier" || containerType === "ImportSpecifier"; 75 | }; 76 | 77 | const memberExpressionComparator = (nodePath, value) => nodePath.matchesPattern(value); 78 | const identifierComparator = (nodePath, value) => nodePath.node.name === value; 79 | const unaryExpressionComparator = (nodePath, value) => nodePath.node.argument.name === value; 80 | const TYPEOF_PREFIX = "typeof "; 81 | 82 | const plugin = function ({ types: t }) { 83 | return { 84 | visitor: { 85 | 86 | // process.env.NODE_ENV; 87 | MemberExpression(nodePath, state) { 88 | processNode(state.opts, nodePath, t.valueToNode, memberExpressionComparator); 89 | }, 90 | 91 | // const x = { version: VERSION }; 92 | Identifier(nodePath, state) { 93 | const binding = nodePath.scope.getBinding(nodePath.node.name); 94 | 95 | if ( 96 | binding 97 | // Don't transform import identifiers. This is meant to mimic webpack's 98 | // DefinePlugin behavior. 99 | || isImportIdentifier(nodePath) 100 | // Do not transform Object keys / properties unless they are computed like {[key]: value} 101 | || nodePath.key === "key" && nodePath.parent.computed === false 102 | || nodePath.key === "property" && nodePath.parent.computed === false 103 | ) { 104 | return; 105 | } 106 | 107 | processNode(state.opts, nodePath, t.valueToNode, identifierComparator); 108 | }, 109 | 110 | // typeof window 111 | UnaryExpression(nodePath, state) { 112 | if (nodePath.node.operator !== "typeof") { return; } 113 | 114 | const { opts } = state; 115 | const keys = Object.keys(opts); 116 | const typeofValues = {}; 117 | 118 | keys.forEach((key) => { 119 | if (key.substring(0, TYPEOF_PREFIX.length) === TYPEOF_PREFIX) { 120 | typeofValues[key.substring(TYPEOF_PREFIX.length)] = opts[key]; 121 | } 122 | }); 123 | 124 | processNode(typeofValues, nodePath, t.valueToNode, unaryExpressionComparator); 125 | } 126 | 127 | } 128 | }; 129 | }; 130 | 131 | // Exports. 132 | module.exports = plugin; 133 | module.exports.default = plugin; 134 | module.exports.getSortedObjectPaths = getSortedObjectPaths; 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-define", 3 | "description": "Babel plugin that replaces member expressions and typeof statements with strings", 4 | "version": "2.1.4", 5 | "contributors": [ 6 | "Eric Baer (https://github.com/baer)", 7 | "Michael Jackson (https://github.com/mjackson)", 8 | "Andy Edwards (https://github.com/jedwards1211)" 9 | ], 10 | "homepage": "https://github.com/FormidableLabs/babel-plugin-transform-define", 11 | "bugs": { 12 | "url": "https://github.com/FormidableLabs/babel-plugin-transform-define/issues" 13 | }, 14 | "repository": "https://github.com/FormidableLabs/babel-plugin-transform-define", 15 | "private": false, 16 | "dependencies": { 17 | "lodash": "^4.17.11", 18 | "traverse": "0.6.6" 19 | }, 20 | "devDependencies": { 21 | "@babel/cli": "^7.15.4", 22 | "@babel/core": "^7.15.5", 23 | "@babel/preset-env": "^7.15.6", 24 | "@changesets/cli": "^2.26.1", 25 | "@svitejs/changesets-changelog-github-compact": "^0.1.1", 26 | "babel-eslint": "^10.0.3", 27 | "eslint": "^7.32.0", 28 | "eslint-config-formidable": "^4.0.0", 29 | "eslint-plugin-filenames": "^1.3.2", 30 | "eslint-plugin-import": "^2.24.2", 31 | "eslint-plugin-promise": "^5.1.0", 32 | "mocha": "^9.1.1" 33 | }, 34 | "main": "lib", 35 | "scripts": { 36 | "preversion": "yarn run check", 37 | "lint": "eslint .", 38 | "test": "mocha test/index.js", 39 | "check": "yarn run lint && yarn run test" 40 | }, 41 | "engines": { 42 | "node": ">= 8.x.x" 43 | }, 44 | "publishConfig": { 45 | "provenance": true 46 | }, 47 | "license": "MIT", 48 | "keywords": [ 49 | "babel-plugin", 50 | "babel-transform", 51 | "babel", 52 | "define", 53 | "DefinePlugin", 54 | "webpack" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - formidable/configurations/es6-node-test 4 | 5 | rules: 6 | no-magic-numbers: off 7 | max-statements: off 8 | no-console: off 9 | 10 | # Various stuff for output. 11 | no-constant-condition: off 12 | no-unused-vars: off 13 | no-undef: off 14 | no-undef-init: off 15 | no-var: off 16 | -------------------------------------------------------------------------------- /test/0/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const x = PRODUCTION; 4 | 5 | if (!PRODUCTION) { 6 | console.log("Debug info"); 7 | } 8 | 9 | if (PRODUCTION) { 10 | console.log("Production log"); 11 | } 12 | -------------------------------------------------------------------------------- /test/0/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = 0; 4 | 5 | if (!0) { 6 | console.log("Debug info"); 7 | } 8 | 9 | if (0) { 10 | console.log("Production log"); 11 | } 12 | -------------------------------------------------------------------------------- /test/binding/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | NO_BINDING; 4 | 5 | var VAR_BINDING = NO_BINDING; 6 | 7 | let LET_BINDING = NO_BINDING; 8 | 9 | const CONST_BINDING = NO_BINDING; 10 | 11 | function HOISTED_BINDING(PARAM_BINDING) { 12 | NO_BINDING; 13 | VAR_BINDING; 14 | LET_BINDING; 15 | CONST_BINDING; 16 | HOISTED_BINDING; 17 | PARAM_BINDING; 18 | } 19 | -------------------------------------------------------------------------------- /test/binding/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | "replaced"; 4 | 5 | var VAR_BINDING = "replaced"; 6 | 7 | var LET_BINDING = "replaced"; 8 | 9 | var CONST_BINDING = "replaced"; 10 | 11 | function HOISTED_BINDING(PARAM_BINDING) { 12 | "replaced"; 13 | VAR_BINDING; 14 | LET_BINDING; 15 | CONST_BINDING; 16 | HOISTED_BINDING; 17 | PARAM_BINDING; 18 | } 19 | -------------------------------------------------------------------------------- /test/emptyString/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = PRODUCTION; 4 | 5 | if (!PRODUCTION) { 6 | console.log("Debug info"); 7 | } 8 | if (PRODUCTION) { 9 | console.log("Production log"); 10 | } 11 | -------------------------------------------------------------------------------- /test/emptyString/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = ""; 4 | 5 | if (!"") { 6 | console.log("Debug info"); 7 | } 8 | 9 | if ("") { 10 | console.log("Production log"); 11 | } 12 | -------------------------------------------------------------------------------- /test/false/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = PRODUCTION; 4 | 5 | if (!PRODUCTION) { 6 | console.log("Debug info"); 7 | } 8 | if (PRODUCTION) { 9 | console.log("Production log"); 10 | } 11 | -------------------------------------------------------------------------------- /test/false/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = false; 4 | 5 | if (!false) { 6 | console.log("Debug info"); 7 | } 8 | if (false) { 9 | console.log("Production log"); 10 | } 11 | -------------------------------------------------------------------------------- /test/identifier/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | VERSION; 4 | 5 | var x = { 6 | version: VERSION 7 | }; 8 | 9 | console.log(VERSION); 10 | 11 | if (!PRODUCTION) { 12 | console.log("Debug info"); 13 | } 14 | 15 | if (PRODUCTION) { 16 | console.log("Production log"); 17 | } 18 | -------------------------------------------------------------------------------- /test/identifier/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | "1.0.0"; 4 | 5 | var x = { 6 | version: "1.0.0" 7 | }; 8 | 9 | console.log("1.0.0"); 10 | 11 | if (!true) { 12 | console.log("Debug info"); 13 | } 14 | 15 | if (true) { 16 | console.log("Production log"); 17 | } 18 | -------------------------------------------------------------------------------- /test/import-identifiers/actual.js: -------------------------------------------------------------------------------- 1 | import DONT_REPLACE_ME, { DONT_REPLACE_ME_EITHER } from 'foo'; 2 | 3 | DONT_REPLACE_ME; 4 | DONT_REPLACE_ME_EITHER; 5 | 6 | function childScope() { 7 | DONT_REPLACE_ME; 8 | DONT_REPLACE_ME_EITHER; 9 | } 10 | -------------------------------------------------------------------------------- /test/import-identifiers/expected.js: -------------------------------------------------------------------------------- 1 | import DONT_REPLACE_ME, { DONT_REPLACE_ME_EITHER } from 'foo'; 2 | 3 | DONT_REPLACE_ME; 4 | DONT_REPLACE_ME_EITHER; 5 | 6 | function childScope() { 7 | DONT_REPLACE_ME; 8 | DONT_REPLACE_ME_EITHER; 9 | } 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const assert = require("assert"); 5 | const fs = require("fs"); 6 | const { EOL } = require("os"); 7 | const { promisify } = require("util"); 8 | 9 | const babel = require("@babel/core"); 10 | const jsdiff = require("diff"); 11 | const chalk = require("chalk"); 12 | 13 | const babelPluginTransformDefine = require("../lib/index.js"); 14 | 15 | const readFile = promisify(fs.readFile); 16 | const splitLines = ({ value }, fn) => value 17 | .split(EOL) 18 | .map((line, idx, lines) => line === "" && idx === lines.length - 1 ? "" : fn(line)) 19 | .join(EOL); 20 | 21 | const assertTransform = async (initial, expected, opts) => { 22 | const transformOpts = { 23 | ...opts, 24 | // Specify filename to pick up local `.babelrc.js` 25 | // https://babeljs.io/docs/en/options#babelrc 26 | filename: initial 27 | }; 28 | 29 | // Note: We trim + EOL to normalize whitespace + give readable error diffs 30 | // Also normalize across OS'es. 31 | const [actualCode, expectedCode] = await Promise.all([ 32 | readFile(initial).then((code) => babel.transform(code, transformOpts).code), 33 | readFile(expected).then((buf) => buf.toString()) 34 | ]).then((vals) => vals.map((val) => val 35 | .split(/\r?\n/) 36 | .join(EOL) 37 | .trim() + EOL 38 | )); 39 | 40 | const diff = jsdiff.diffLines(actualCode, expectedCode); 41 | // Consider no diff or newline-only diff to be "the same". 42 | const changes = diff.filter(({ added, removed, value }) => (added || removed) && value.trim()); 43 | if (changes.length === 0) { 44 | return true; 45 | } 46 | 47 | const msg = diff 48 | .map((obj) => { 49 | if (obj.added) { return splitLines(obj, (line) => chalk `{green +${line}}`); } 50 | if (obj.removed) { return splitLines(obj, (line) => chalk `{red -${line}}`); } 51 | return splitLines(obj, (line) => chalk `{grey ${line}}`); 52 | }) 53 | .join(""); 54 | 55 | throw new Error(chalk `{white Difference found ({green actual}, {red expected}): ${EOL}}${msg}`); 56 | }; 57 | 58 | const getBabelOps = (pluginOps) => ({ 59 | presets: ["@babel/preset-env"], 60 | plugins: [ 61 | [path.resolve(__dirname, "../lib/index.js"), pluginOps] 62 | ] 63 | }); 64 | 65 | describe("babel-plugin-transform-define", () => { 66 | before(function () { 67 | // TODO: See if needed. Previously here to warm up babel for tests. 68 | this.timeout(10000); // eslint-disable-line 69 | babel.transform("const x = 1;", getBabelOps()); 70 | }); 71 | 72 | describe("transformation tests", () => { 73 | describe("Member Expressions", () => { 74 | it("should transform with config defined by String keys", () => { 75 | const babelOpts = getBabelOps({ 76 | "process.env.NODE_ENV": "development" 77 | }); 78 | 79 | return assertTransform( 80 | path.join(__dirname, "./member-expression/actual.js"), 81 | path.join(__dirname, "./member-expression/expected.js"), babelOpts); 82 | }); 83 | 84 | it("should transform with config defined by an Object", () => { 85 | const babelOpts = getBabelOps({ 86 | process: { 87 | env: { 88 | NODE_ENV: "development" 89 | } 90 | } 91 | }); 92 | 93 | return assertTransform( 94 | path.join(__dirname, "./member-expression/actual.js"), 95 | path.join(__dirname, "./member-expression/expected.js"), babelOpts); 96 | }); 97 | }); 98 | 99 | it("should transform Unary Expressions", () => { 100 | const babelOpts = getBabelOps({ 101 | "typeof window": "object" 102 | }); 103 | 104 | return assertTransform( 105 | path.join(__dirname, "./unary-expression/actual.js"), 106 | path.join(__dirname, "./unary-expression/expected.js"), babelOpts); 107 | }); 108 | 109 | it("should transform Identifiers", () => { 110 | const babelOpts = getBabelOps({ 111 | VERSION: "1.0.0", 112 | PRODUCTION: true 113 | }); 114 | 115 | return assertTransform( 116 | path.join(__dirname, "./identifier/actual.js"), 117 | path.join(__dirname, "./identifier/expected.js"), babelOpts); 118 | }); 119 | 120 | it("should transform false", () => { 121 | const babelOpts = getBabelOps({ 122 | PRODUCTION: false 123 | }); 124 | 125 | return assertTransform( 126 | path.join(__dirname, "./false/actual.js"), 127 | path.join(__dirname, "./false/expected.js"), babelOpts); 128 | }); 129 | 130 | it("should transform 0", () => { 131 | const babelOpts = getBabelOps({ 132 | PRODUCTION: 0 133 | }); 134 | 135 | return assertTransform( 136 | path.join(__dirname, "./0/actual.js"), 137 | path.join(__dirname, "./0/expected.js"), babelOpts); 138 | }); 139 | 140 | it("should transform empty string", () => { 141 | const babelOpts = getBabelOps({ 142 | PRODUCTION: "" 143 | }); 144 | 145 | return assertTransform( 146 | path.join(__dirname, "./emptyString/actual.js"), 147 | path.join(__dirname, "./emptyString/expected.js"), babelOpts); 148 | }); 149 | 150 | it("should transform null", () => { 151 | const babelOpts = getBabelOps({ 152 | PRODUCTION: null 153 | }); 154 | 155 | return assertTransform( 156 | path.join(__dirname, "./null/actual.js"), 157 | path.join(__dirname, "./null/expected.js"), babelOpts); 158 | }); 159 | 160 | it("should transform undefined", () => { 161 | const babelOpts = getBabelOps({ 162 | PRODUCTION: undefined 163 | }); 164 | 165 | return assertTransform( 166 | path.join(__dirname, "./undefined/actual.js"), 167 | path.join(__dirname, "./undefined/expected.js"), babelOpts); 168 | }); 169 | 170 | it("should transform code from dynamic .babelrc.js", () => assertTransform( 171 | path.join(__dirname, "./load-dynamic-babelrc/actual.js"), 172 | path.join(__dirname, "./load-dynamic-babelrc/expected.js") 173 | )); 174 | 175 | it("should not transform import identifiers", () => { 176 | // Don't use `getBabelOpts` here cause we want to avoid preset-env which 177 | // injects a bunch of extra code to handle es modules. It makes the test 178 | // output harder to read. 179 | const babelOpts = { 180 | plugins: [ 181 | [path.resolve(__dirname, "../lib/index.js"), { DONT_REPLACE_ME: "injected" }] 182 | ] 183 | }; 184 | 185 | return assertTransform( 186 | path.join(__dirname, "./import-identifiers/actual.js"), 187 | path.join(__dirname, "./import-identifiers/expected.js"), babelOpts); 188 | }); 189 | 190 | it("should not transform identifiers with binding", () => { 191 | const babelOpts = getBabelOps({ 192 | NO_BINDING: "replaced", 193 | VAR_BINDING: "replaced", 194 | LET_BINDING: "replaced", 195 | CONST_BINDING: "replaced", 196 | HOISTED_BINDING: "replaced", 197 | PARAM_BINDING: "replaced" 198 | }); 199 | 200 | return assertTransform( 201 | path.join(__dirname, "./binding/actual.js"), 202 | path.join(__dirname, "./binding/expected.js"), babelOpts); 203 | }); 204 | 205 | it("should not transform object keys / properties unless they are computed", () => { 206 | const babelOpts = getBabelOps({ 207 | __DEV__: true, 208 | __DEV2__: "true" 209 | }); 210 | 211 | return assertTransform( 212 | path.join(__dirname, "./object-keys-properties/actual.js"), 213 | path.join(__dirname, "./object-keys-properties/expected.js"), babelOpts); 214 | }); 215 | }); 216 | 217 | describe("unit tests", () => { 218 | describe("getSortedObjectPaths", () => { 219 | it("should return an array", () => { 220 | let objectPaths = babelPluginTransformDefine.getSortedObjectPaths(null); 221 | assert(Array.isArray(objectPaths)); 222 | objectPaths = babelPluginTransformDefine.getSortedObjectPaths(undefined); 223 | assert(Array.isArray(objectPaths)); 224 | objectPaths = babelPluginTransformDefine.getSortedObjectPaths(); 225 | assert(Array.isArray(objectPaths)); 226 | objectPaths = babelPluginTransformDefine.getSortedObjectPaths({}); 227 | assert(Array.isArray(objectPaths)); 228 | objectPaths = babelPluginTransformDefine.getSortedObjectPaths({ process: "env" }); 229 | assert(Array.isArray(objectPaths)); 230 | }); 231 | 232 | it("should return a complete list of paths", () => { 233 | const obj = { process: { env: { NODE_ENV: "development" } } }; 234 | const objectPaths = babelPluginTransformDefine.getSortedObjectPaths(obj); 235 | assert.deepEqual(objectPaths, ["process.env.NODE_ENV", "process.env", "process"]); 236 | }); 237 | 238 | it("should return a list sorted by length", () => { 239 | const obj = { process: { env: { NODE_ENV: "development" } } }; 240 | const objectPaths = babelPluginTransformDefine.getSortedObjectPaths(obj); 241 | assert.deepEqual(objectPaths, objectPaths.sort((a, b) => b.length - a.length)); 242 | }); 243 | }); 244 | }); 245 | }); 246 | -------------------------------------------------------------------------------- /test/load-dynamic-babelrc/.babelrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Dynamic example. 5 | * 6 | * Can use any of available `.babelrc.js` functionality to afford dynamic 7 | * behavior and overrides. 8 | */ 9 | 10 | const path = require("path"); 11 | const plugin = path.resolve(__dirname, "../../lib/index.js") 12 | 13 | module.exports = { 14 | presets: ["@babel/preset-env"], 15 | plugins: [ 16 | [plugin, { 17 | "process.env.NODE_ENV": "development" 18 | }] 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /test/load-dynamic-babelrc/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | process.env.NODE_ENV; 4 | process.env.NODE_ENV === "development"; 5 | -------------------------------------------------------------------------------- /test/load-dynamic-babelrc/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | "development"; 4 | true; 5 | -------------------------------------------------------------------------------- /test/member-expression/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | process.env.NODE_ENV; 4 | process.env.NODE_ENV === "development"; 5 | -------------------------------------------------------------------------------- /test/member-expression/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | "development"; 4 | true; 5 | -------------------------------------------------------------------------------- /test/null/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = PRODUCTION; 4 | 5 | if (!PRODUCTION) { 6 | console.log("Debug info"); 7 | } 8 | 9 | if (PRODUCTION) { 10 | console.log("Production log"); 11 | } 12 | -------------------------------------------------------------------------------- /test/null/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = null; 4 | 5 | if (!null) { 6 | console.log("Debug info"); 7 | } 8 | 9 | if (null) { 10 | console.log("Production log"); 11 | } 12 | -------------------------------------------------------------------------------- /test/object-keys-properties/actual.js: -------------------------------------------------------------------------------- 1 | const obj = { 2 | __DEV__ 3 | }; 4 | const obj1 = { 5 | __DEV__: "test" 6 | }; 7 | const obj2 = { 8 | __DEV__: __DEV__ 9 | }; 10 | const obj3 = { 11 | "__DEV__": __DEV__ 12 | }; 13 | const obj4 = { 14 | ["__DEV__"]: __DEV__ 15 | }; 16 | 17 | const obj5 = { 18 | [__DEV__]: __DEV__ 19 | }; 20 | 21 | const obj6 = { 22 | [__DEV2__]: __DEV2__ 23 | }; 24 | 25 | 26 | const access = obj.__DEV__; 27 | const access1 = obj[__DEV2__]; 28 | const access2 = obj["__DEV__"]; -------------------------------------------------------------------------------- /test/object-keys-properties/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 4 | 5 | var obj = { 6 | __DEV__: true 7 | }; 8 | var obj1 = { 9 | __DEV__: "test" 10 | }; 11 | var obj2 = { 12 | __DEV__: true 13 | }; 14 | var obj3 = { 15 | "__DEV__": true 16 | }; 17 | 18 | var obj4 = _defineProperty({}, "__DEV__", true); 19 | 20 | var obj5 = _defineProperty({}, true, true); 21 | 22 | var obj6 = _defineProperty({}, "true", "true"); 23 | 24 | var access = obj.__DEV__; 25 | var access1 = obj["true"]; 26 | var access2 = obj["__DEV__"]; -------------------------------------------------------------------------------- /test/unary-expression/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | typeof window; 4 | typeof window === "object"; 5 | -------------------------------------------------------------------------------- /test/unary-expression/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | "object"; 4 | true; 5 | -------------------------------------------------------------------------------- /test/undefined/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = PRODUCTION; 4 | 5 | if (!PRODUCTION) { 6 | console.log("Debug info"); 7 | } 8 | 9 | if (PRODUCTION) { 10 | console.log("Production log"); 11 | } 12 | -------------------------------------------------------------------------------- /test/undefined/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var x = undefined; 4 | 5 | if (!undefined) { 6 | console.log("Debug info"); 7 | } 8 | 9 | if (undefined) { 10 | console.log("Production log"); 11 | } 12 | --------------------------------------------------------------------------------