|
| 1 | +# Source file transforms |
| 2 | + |
| 3 | +This directory contains home-grown transforms for the build systems of the MetaMask applications. |
| 4 | + |
| 5 | +## Remove Fenced Code |
| 6 | + |
| 7 | +> `./remove-fenced-code.ts` |
| 8 | +
|
| 9 | +### Usage |
| 10 | + |
| 11 | +Let's imagine you've added some fences to your source code. |
| 12 | + |
| 13 | +```typescript |
| 14 | +this.store.updateStructure({ |
| 15 | + /** ..., */ |
| 16 | + GasFeeController: this.gasFeeController, |
| 17 | + TokenListController: this.tokenListController, |
| 18 | + ///: BEGIN:ONLY_INCLUDE_IF(snaps) |
| 19 | + SnapController: this.snapController, |
| 20 | + ///: END:ONLY_INCLUDE_IF |
| 21 | +}); |
| 22 | +``` |
| 23 | + |
| 24 | +The transform should be applied on your raw source files as they are committed to |
| 25 | +your repository, before anything else (e.g. Babel, `tsc`, etc.) parses or modifies them. |
| 26 | + |
| 27 | +```typescript |
| 28 | +import { |
| 29 | + FeatureLabels, |
| 30 | + removeFencedCode, |
| 31 | + lintTransformedFile, |
| 32 | +} from '@metamask/build-utils'; |
| 33 | + |
| 34 | +// Let's imagine this function exists in your build system and is called immediately |
| 35 | +// after your source files are read from disk. |
| 36 | +async function applyTransforms( |
| 37 | + filePath: string, |
| 38 | + fileContent: string, |
| 39 | + features: FeatureLabels, |
| 40 | + shouldLintTransformedFiles: boolean = true, |
| 41 | +): string { |
| 42 | + const [newFileContent, wasModified] = removeFencedCode( |
| 43 | + filePath, |
| 44 | + fileContent, |
| 45 | + features, |
| 46 | + ); |
| 47 | + |
| 48 | + // You may choose to disable linting during e.g. dev builds since lint failures cause |
| 49 | + // an error to be thrown. |
| 50 | + if (wasModified && shouldLintTransformedFiles) { |
| 51 | + // You probably only need a singleton ESLint instance for your linting purposes. |
| 52 | + // See the lintTransformedFile documentation for important notes about usage. |
| 53 | + const eslintInstance = getESLintInstance(); |
| 54 | + await lintTransformedFile(eslintInstance, filePath, newFileContent); |
| 55 | + } |
| 56 | + return newFileContent; |
| 57 | +} |
| 58 | + |
| 59 | +// Then, in the relevant part of your build process... |
| 60 | + |
| 61 | +const features: FeatureLabels = { |
| 62 | + active: new Set(['foo']), // Fences with these features will be included. |
| 63 | + all: new Set(['snaps', 'foo' /** etc. */]), // All extant features must be listed here. |
| 64 | +}; |
| 65 | + |
| 66 | +const transformedFile = await applyTransforms( |
| 67 | + filePath, |
| 68 | + fileContent, |
| 69 | + features, |
| 70 | + shouldLintTransformedFiles, |
| 71 | +); |
| 72 | + |
| 73 | +// Do something with the results. |
| 74 | +// continueBuildProcess(transformedFile); |
| 75 | +``` |
| 76 | + |
| 77 | +After the transform has been applied as above, the example source code will look like this: |
| 78 | + |
| 79 | +```typescript |
| 80 | +this.store.updateStructure({ |
| 81 | + /** ..., */ |
| 82 | + GasFeeController: this.gasFeeController, |
| 83 | + TokenListController: this.tokenListController, |
| 84 | +}); |
| 85 | +``` |
| 86 | + |
| 87 | +### Overview |
| 88 | + |
| 89 | +When creating builds that support different features, it is desirable to exclude |
| 90 | +unsupported features, files, and dependencies at build time. Undesired files and |
| 91 | +dependencies can be excluded wholesale, but the _use_ of undesired modules in |
| 92 | +files that should otherwise be included – i.e. import statements and references |
| 93 | +to those imports – cannot. |
| 94 | + |
| 95 | +To support the exclusion of the use of undesired modules at build time, we |
| 96 | +introduce the concept of code fencing to our build system. Our code fencing |
| 97 | +syntax amounts to a tiny DSL, which is specified below. |
| 98 | + |
| 99 | +The transform expects to receive the contents of individual files as a single string, |
| 100 | +which it will parse in order to identify any code fences. If any fences that should not |
| 101 | +be included in the current build are found, the fences and the lines that they wrap |
| 102 | +are deleted. An error is thrown if a malformed fence is identified. |
| 103 | + |
| 104 | +For example, the following fenced code: |
| 105 | + |
| 106 | +```javascript |
| 107 | +this.store.updateStructure({ |
| 108 | + ..., |
| 109 | + GasFeeController: this.gasFeeController, |
| 110 | + TokenListController: this.tokenListController, |
| 111 | + ///: BEGIN:ONLY_INCLUDE_IF(snaps) |
| 112 | + SnapController: this.snapController, |
| 113 | + ///: END:ONLY_INCLUDE_IF |
| 114 | +}); |
| 115 | +``` |
| 116 | + |
| 117 | +Is transformed as follows if the current build should not include the `snaps` feature: |
| 118 | + |
| 119 | +```javascript |
| 120 | +this.store.updateStructure({ |
| 121 | + ..., |
| 122 | + GasFeeController: this.gasFeeController, |
| 123 | + TokenListController: this.tokenListController, |
| 124 | +}); |
| 125 | +``` |
| 126 | + |
| 127 | +Note that multiple features can be specified by separating them with |
| 128 | +commands inside the parameter parentheses: |
| 129 | + |
| 130 | +```javascript |
| 131 | +///: BEGIN:ONLY_INCLUDE_IF(build-beta,build-flask) |
| 132 | +``` |
| 133 | + |
| 134 | +### Code Fencing Syntax |
| 135 | + |
| 136 | +> In the specification, angle brackets, `< >`, indicate required tokens, while |
| 137 | +> straight brackets, `[ ]`, indicate optional tokens. |
| 138 | +> |
| 139 | +> Alphabetical characters identify the name and purpose of a token. All other |
| 140 | +> characters, including parentheses, `( )`, are literals. |
| 141 | +
|
| 142 | +A fence line is a single-line JavaScript comment, optionally surrounded by |
| 143 | +whitespace, in the following format: |
| 144 | + |
| 145 | +```text |
| 146 | +///: <terminus>:<command>[(parameters)] |
| 147 | +
|
| 148 | +|__| |________________________________| |
| 149 | + | | |
| 150 | + | | |
| 151 | +sentinel directive |
| 152 | +``` |
| 153 | + |
| 154 | +The first part of a fence line is the **sentinel** which is always the string |
| 155 | +"`///:`". If the first four non-whitespace characters of a line are not exactly the |
| 156 | +**sentinel** the line will be ignored by the parser. The **sentinel** must be |
| 157 | +succeeded by a single space character, or parsing will fail. |
| 158 | + |
| 159 | +The remainder of the fence line is called the **directive** |
| 160 | +The directive consists of a **terminus** **command** and **parameters** |
| 161 | + |
| 162 | +- The **terminus** is one of the strings `BEGIN` and `END`. It must be followed by |
| 163 | + a single colon, `:`. |
| 164 | +- The **command** is a string of uppercase alphabetical characters, optionally |
| 165 | + including underscores, `_`. The possible commands are listed later in this |
| 166 | + specification. |
| 167 | +- The **parameters** are a string of comma-separated RegEx `\w` strings. The parameters |
| 168 | + string must be parenthesized, only specified for `BEGIN` directives, and valid for its |
| 169 | + command. |
| 170 | + |
| 171 | +A valid code fence consists of two fence lines surrounding one or more lines of |
| 172 | +non-fence lines. The first fence line must consist of a `BEGIN` directive, and |
| 173 | +the second an `END` directive. The command of both directives must be the same, |
| 174 | +and the parameters (if any) must be valid for the command. Nesting is not intended |
| 175 | +to be supported, and may produce undefined behavior. |
| 176 | + |
| 177 | +If an invalid fence is detected, parsing will fail, and the transform will throw |
| 178 | +an error. |
| 179 | + |
| 180 | +### Commands |
| 181 | + |
| 182 | +#### `ONLY_INCLUDE_IF` |
| 183 | + |
| 184 | +This, the only command defined so far, is used to exclude lines of code depending |
| 185 | +on flags provided to the current build process. If a particular set of lines should |
| 186 | +only be included in e.g. the beta build type, they should be wrapped as follows: |
| 187 | + |
| 188 | +```javascript |
| 189 | +///: BEGIN:ONLY_INCLUDE_IF(build-beta) |
| 190 | +console.log('I am only included in beta builds.'); |
| 191 | +///: END:ONLY_INCLUDE_IF |
| 192 | +``` |
| 193 | + |
| 194 | +At build time, the fences and the fenced lines will be removed if the `build-beta` |
| 195 | +flag is not provided to the transform. |
| 196 | + |
| 197 | +The parameters must be provided as a comma-separated list of features that are |
| 198 | +valid per the consumer's build system. |
0 commit comments