babel-preset-startupjs 0.60.0-canary.0 → 0.60.0-canary.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +231 -2
  2. package/package.json +5 -4
  3. package/utils.js +35 -0
package/index.js CHANGED
@@ -1,4 +1,71 @@
1
- module.exports = (api, { platform, env } = {}) => {
1
+ /**
2
+ * Compilation pipeline:
3
+ * 1. Transform pug to jsx
4
+ * 2. Auto-load startupjs plugins
5
+ * 3. Run eliminator to remove code targeting other envs
6
+ * 4. Transform CSS modules
7
+ *
8
+ * Options:
9
+ * platform - Force platform to compile to (e.g. 'ios', 'android', 'web').
10
+ * Default: 'web' (or auto-detected on React Native)
11
+ * On React Native (Metro) this gets automatically detected inside babel plugins.
12
+ * reactType - Force the React type - RN or pure web React (e.g. 'react-native', 'web').
13
+ * Default: undefined (auto-detected)
14
+ * This shouldn't be needed in most cases since it will be automatically detected.
15
+ * cache - Force the CSS caching library instance (e.g. 'teamplay').
16
+ * Default: undefined (auto-detected)
17
+ * This shouldn't be needed in most cases since it will be automatically detected.
18
+ * transformPug - Whether to transform pug to jsx.
19
+ * Default: true
20
+ * transformCss - Whether to transform CSS modules (styl/css files and styl`` css`` in JSX).
21
+ * Default: true
22
+ * useRequireContext - Whether to use require.context for loading startupjs plugins.
23
+ * The underlying environment must support require.context (e.g. Metro, Webpack).
24
+ * Default: true
25
+ * clientOnly - Whether to transform model/*.js files to keep only the client-relevant code.
26
+ * Default: true
27
+ * This option is required when building for the client (React Native or web) so that
28
+ * server-only code is removed from model files and secret information is not leaked to the client.
29
+ * envs - Array of envs to keep during code elimination of the startupjs config and plugins.
30
+ * Default: ['features', 'isomorphic', 'client']
31
+ * On the server, this should usually include 'server' instead of 'client':
32
+ * ['features', 'isomorphic', 'server']
33
+ * isStartupjsFile - A function (filename, code) => boolean that checks whether the given file
34
+ * is part of the startupjs ecosystem (a plugin, startupjs.config.js, loadStartupjsConfig.js or a model file).
35
+ * Default: a function that returns true for all startupjs plugin ecosystem files. And also
36
+ * when clientOnly is true, it also returns true for model/*.js files to keep only client-relevant code there.
37
+ */
38
+ const { createStartupjsFileChecker, CONFIG_FILENAME_REGEX } = require('./utils.js')
39
+ const PLUGIN_KEYS = ['name', 'for', 'order', 'enabled']
40
+ const PROJECT_KEYS = ['plugins', 'modules']
41
+ const ALL_ENVS = ['features', 'isomorphic', 'client', 'server', 'build']
42
+ const MAGIC_IMPORTS = ['startupjs/registry', '@startupjs/registry']
43
+
44
+ module.exports = (api, {
45
+ platform,
46
+ reactType,
47
+ cache,
48
+ compileCssImports,
49
+ cssFileExtensions,
50
+ transformCss = true,
51
+ transformPug = true,
52
+ useRequireContext = true,
53
+ clientOnly = true,
54
+ envs = ['features', 'isomorphic', 'client'],
55
+ isStartupjsFile = createStartupjsFileChecker({ clientOnly })
56
+ } = {}) => {
57
+ const isMetro = api.caller(caller => caller?.name === 'metro')
58
+
59
+ // By default on Metro we don't need to compile CSS imports since we are relying on the custom
60
+ // StartupJS metro-babel-transformer which handles CSS imports as separate files.
61
+ if (compileCssImports == null && isMetro) compileCssImports = false
62
+
63
+ // on Metro we transform any CSS imports since StartupJS metro-babel-transformer
64
+ // turns off Expo's default CSS support and handles only our CSS imports.
65
+ // When used in a plain Web project though though,
66
+ // we want to only handle the default ['cssx.css', 'cssx.styl'] extensions.
67
+ if (cssFileExtensions == null && isMetro) cssFileExtensions = ['styl', 'css']
68
+
2
69
  return {
3
70
  overrides: [{
4
71
  test: isJsxSource,
@@ -22,8 +89,170 @@ module.exports = (api, { platform, env } = {}) => {
22
89
  ]
23
90
  }, {
24
91
  plugins: [
92
+ // transform pug to jsx. This generates a bunch of new AST nodes
93
+ // (it's important to do this first before any dead code elimination runs)
94
+ transformPug && [require('cssxjs/babel/plugin-react-pug'), { classAttribute: 'styleName' }],
95
+
96
+ // inline CSS modules (styl`` in the same JSX file -- similar to how it is in Vue.js)
97
+ transformCss && [require('cssxjs/babel/plugin-rn-stylename-inline'), {
98
+ platform
99
+ }],
100
+ // CSS modules (separate .styl/.css file)
101
+ transformCss && [require('cssxjs/babel/plugin-rn-stylename-to-style'), {
102
+ extensions: cssFileExtensions,
103
+ useImport: true,
104
+ reactType,
105
+ cache,
106
+ compileCssImports
107
+ }],
108
+
109
+ // auto-load startupjs plugins
110
+ // traverse "exports" of package.json and all dependencies to find all startupjs plugins
111
+ // and automatically import them in the main startupjs.config.js file
112
+ [require('@startupjs/babel-plugin-startupjs-plugins'), { useRequireContext }],
113
+
114
+ // run eliminator to remove code targeting other envs.
115
+ // For example, only keep code related to 'client' and 'isomorphic' envs
116
+ // (in which case any code related to 'server' and 'build' envs will be removed)
117
+ [require('@startupjs/babel-plugin-eliminator'), {
118
+ shouldTransformFileChecker: isStartupjsFile,
119
+ trimObjects: [{
120
+ magicFilenameRegex: CONFIG_FILENAME_REGEX,
121
+ magicExport: 'default',
122
+ targetObjectJsonPath: '$.modules.*',
123
+ ensureOnlyKeys: ALL_ENVS,
124
+ keepKeys: envs
125
+ }, {
126
+ magicFilenameRegex: CONFIG_FILENAME_REGEX,
127
+ magicExport: 'default',
128
+ targetObjectJsonPath: '$.plugins.*',
129
+ ensureOnlyKeys: ALL_ENVS,
130
+ keepKeys: envs
131
+ }, {
132
+ magicFilenameRegex: CONFIG_FILENAME_REGEX,
133
+ magicExport: 'default',
134
+ targetObjectJsonPath: '$',
135
+ // envs on the top level are the alias for '$.modules.startupjs'
136
+ ensureOnlyKeys: [...PROJECT_KEYS, ...ALL_ENVS],
137
+ keepKeys: [...PROJECT_KEYS, ...envs]
138
+ }, {
139
+ functionName: 'createPlugin',
140
+ magicImports: MAGIC_IMPORTS,
141
+ ensureOnlyKeys: [...PLUGIN_KEYS, ...ALL_ENVS],
142
+ keepKeys: [...PLUGIN_KEYS, ...envs]
143
+ }],
144
+ ...(clientOnly
145
+ ? {
146
+ transformFunctionCalls: [{
147
+ // direct named exports of aggregation() within model/*.js files
148
+ // are replaced with aggregationHeader() calls.
149
+ // 'collection' is the filename without extension
150
+ // 'name' is the direct named export const name
151
+ //
152
+ // Example:
153
+ //
154
+ // // in model/games.js
155
+ // export const $$byGameId = aggregation(({ gameId }) => ({ gameId }))
156
+ //
157
+ // will be replaced with:
158
+ //
159
+ // __aggregationHeader({ collection: 'games', name: '$$byGameId' })
160
+ //
161
+ functionName: 'aggregation',
162
+ magicImports: ['startupjs'],
163
+ requirements: {
164
+ argumentsAmount: 1,
165
+ directNamedExportedAsConst: true
166
+ },
167
+ replaceWith: {
168
+ newFunctionNameFromSameImport: '__aggregationHeader',
169
+ newCallArgumentsTemplate: `[
170
+ {
171
+ collection: %%filenameWithoutExtension%%,
172
+ name: %%directNamedExportConstName%%
173
+ }
174
+ ]`
175
+ }
176
+ }, {
177
+ // export default inside of aggregation() within a separate model/*.$$myAggregation.js files
178
+ // are replaced with aggregationHeader() calls.
179
+ // Filepath is stripped of the extensions and split into sections (by dots and slashes)
180
+ // 'name' is the last section.
181
+ // 'collection' is the section before it.
182
+ //
183
+ // Example:
184
+ //
185
+ // // in model/games/$$active.js
186
+ // export default aggregation(({ gameId }) => ({ gameId }))
187
+ //
188
+ // will be replaced with:
189
+ //
190
+ // __aggregationHeader({ collection: 'games', name: '$$active' })
191
+ //
192
+ functionName: 'aggregation',
193
+ magicImports: ['startupjs'],
194
+ requirements: {
195
+ argumentsAmount: 1,
196
+ directDefaultExported: true
197
+ },
198
+ replaceWith: {
199
+ newFunctionNameFromSameImport: '__aggregationHeader',
200
+ newCallArgumentsTemplate: `[
201
+ {
202
+ collection: %%folderAndFilenameWithoutExtension%%.split(/[\\\\/\\.]/).at(-2),
203
+ name: %%folderAndFilenameWithoutExtension%%.split(/[\\\\/\\.]/).at(-1)
204
+ }
205
+ ]`
206
+ }
207
+ }, {
208
+ // TODO: this has to be implemented! It's not actually working yet.
209
+
210
+ // any other calls to aggregation() must explicitly define the collection and name
211
+ // as the second argument. If not, the build will fail.
212
+ //
213
+ // Example:
214
+ //
215
+ // aggregation(
216
+ // ({ gameId }) => ({ gameId }),
217
+ // { collection: 'games', name: 'byGameId' }
218
+ // )
219
+ //
220
+ // will be replaced with:
221
+ //
222
+ // __aggregationHeader({ collection: 'games', name: 'byGameId' })
223
+ //
224
+ functionName: 'aggregation',
225
+ magicImports: ['startupjs'],
226
+ requirements: {
227
+ argumentsAmount: 2
228
+ },
229
+ throwIfRequirementsNotMet: true,
230
+ replaceWith: {
231
+ newFunctionNameFromSameImport: '__aggregationHeader',
232
+ newCallArgumentsTemplate: '[%%argument1%%]' // 0-based index
233
+ }
234
+ }, {
235
+ // remove accessControl() calls (replace with undefined)
236
+ functionName: 'accessControl',
237
+ magicImports: ['startupjs'],
238
+ replaceWith: {
239
+ remove: true // replace the whole function call with undefined
240
+ }
241
+ }, {
242
+ // remove serverOnly() calls (replace with undefined)
243
+ functionName: 'serverOnly',
244
+ magicImports: ['startupjs'],
245
+ replaceWith: {
246
+ remove: true // replace the whole function call with undefined
247
+ }
248
+ }]
249
+ }
250
+ : {}
251
+ )
252
+ }],
253
+
25
254
  // debugging features
26
- env === 'development' && require('@startupjs/babel-plugin-startupjs-debug'),
255
+ require('@startupjs/babel-plugin-startupjs-debug'),
27
256
  require('@startupjs/babel-plugin-i18n-extract')
28
257
  ].filter(Boolean)
29
258
  }]
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "babel-preset-startupjs",
3
- "version": "0.60.0-canary.0",
3
+ "version": "0.60.0-canary.5",
4
4
  "description": "Babel preset for compiling StartupJS app on server, web, native",
5
5
  "main": "index.js",
6
6
  "exports": {
7
- ".": "./index.js"
7
+ ".": "./index.js",
8
+ "./utils": "./utils.js"
8
9
  },
9
10
  "publishConfig": {
10
11
  "access": "public"
@@ -16,7 +17,7 @@
16
17
  "@babel/plugin-syntax-typescript": "^7.23.3",
17
18
  "@startupjs/babel-plugin-i18n-extract": "^0.60.0-canary.0",
18
19
  "@startupjs/babel-plugin-startupjs": "^0.60.0-canary.0",
19
- "@startupjs/babel-plugin-startupjs-debug": "^0.60.0-canary.0"
20
+ "@startupjs/babel-plugin-startupjs-debug": "^0.60.0-canary.2"
20
21
  },
21
- "gitHead": "4c5baa750402d0beb02921a38413620b348bd374"
22
+ "gitHead": "8ff5595765304c69e179acdfc8bb303e0d7f2ced"
22
23
  }
package/utils.js ADDED
@@ -0,0 +1,35 @@
1
+ // NOTE: startupjs.config.cjs is used by the old bundler. This regex does not include it.
2
+ exports.CONFIG_FILENAME_REGEX = /(?:^|[\\/])startupjs\.config\.m?[jt]sx?$/
3
+ exports.PLUGIN_FILENAME_REGEX = /(?:^|[.\\/])plugin\.[mc]?[jt]sx?$/
4
+ exports.LOAD_CONFIG_FILENAME_REGEX = /(?:^|[\\/])loadStartupjsConfig\.m?[jt]sx?$/
5
+ exports.MODEL_FILENAME_REGEX = /(?:^|[.\\/])model[\\/].*\.[mc]?[jt]sx?$/
6
+ const STARTUPJS_FILE_CONTENT_REGEX = /['"]startupjs['"]/
7
+
8
+ exports.isStartupjsPluginEcosystemFile = filename => {
9
+ return (
10
+ exports.PLUGIN_FILENAME_REGEX.test(filename) ||
11
+ exports.CONFIG_FILENAME_REGEX.test(filename) ||
12
+ exports.LOAD_CONFIG_FILENAME_REGEX.test(filename)
13
+ )
14
+ }
15
+
16
+ exports.isModelFile = (filename, code) => {
17
+ return exports.MODEL_FILENAME_REGEX.test(filename) && STARTUPJS_FILE_CONTENT_REGEX.test(code)
18
+ }
19
+
20
+ exports.createStartupjsFileChecker = ({ clientOnly } = {}) => {
21
+ return (filename, code) => {
22
+ if (exports.isStartupjsPluginEcosystemFile(filename)) return true
23
+ if (clientOnly) {
24
+ if (code != null) {
25
+ if (exports.isModelFile(filename, code)) return true
26
+ } else {
27
+ console.warn(
28
+ '[babel-preset-startupjs] File\'s source code must be provided when clientOnly is true. ' +
29
+ 'Assuming false for isModelFile check. Filename:', filename
30
+ )
31
+ }
32
+ }
33
+ return false
34
+ }
35
+ }