@cssxjs/bundler 0.2.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # v0.2.0 (Tue Nov 04 2025)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - feat: make it work for pure web through a babel plugin [#2](https://github.com/startupjs/cssx/pull/2) ([@cray0000](https://github.com/cray0000))
6
+ - feat: move over styles-related packages from startupjs ([@cray0000](https://github.com/cray0000))
7
+
8
+ #### ⚠️ Pushed to `master`
9
+
10
+ - tests: create a general test command which loops through all packages and runs tests. Update tests for babel-plugin-rn-stylename-to-style ([@cray0000](https://github.com/cray0000))
11
+
12
+ #### Authors: 1
13
+
14
+ - Pavel Zhukov ([@cray0000](https://github.com/cray0000))
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # cssx bundler
2
+
3
+ > Compile CSSX styles in React Native and Web bundlers
4
+
5
+ ## Usage
6
+
7
+ TBD
8
+
9
+ ## Export from stylus and css files
10
+
11
+ We can export variables directly from stylus into js. Here is an example:
12
+
13
+ 1. In `ShoppingCart/index.styl` file:
14
+
15
+ ```styl
16
+ // allow overrides from global configuration
17
+ $this = merge({
18
+ bgColor: $UI.colors.primary,
19
+ height: 10u
20
+ }, $UI.ShoppingCart, true)
21
+
22
+ .root
23
+ height: $this.height
24
+ background-color: $this.bgColor
25
+
26
+ :export
27
+ config: $this
28
+ colors: $UI.colors
29
+ foobar: 42
30
+ ```
31
+
32
+ 2. Now import variables `colors`, `config` and `foobar` in the `ShoppingCart/index.js` file:
33
+
34
+ ```jsx
35
+ import STYLES from './index.styl'
36
+
37
+ const {
38
+ config: { bgColor },
39
+ colors,
40
+ foobar
41
+ } = STYLES
42
+
43
+ export default function ShoppingCart () {
44
+ console.log('Background color is:', bgColor)
45
+ console.log('Available colors:', colors)
46
+ console.log('Magic number FooBar:', foobar)
47
+ return <View styleName='root' />
48
+ }
49
+ ```
50
+
51
+ ## MIT License
52
+
53
+ Copyright (c) 2018 Pavel Zhukov
@@ -0,0 +1,22 @@
1
+ // Simple mock to be able to call simple webpack loaders with filename substitution.
2
+ module.exports = function callLoader (loader, source, filename, options = {}) {
3
+ let isAsync = false
4
+ let resolveAsync
5
+ let rejectAsync
6
+ const markAsync = () => {
7
+ isAsync = true
8
+ return (err, result) => {
9
+ if (err) return rejectAsync(err)
10
+ resolveAsync(result)
11
+ }
12
+ }
13
+ const result = loader.call({ resourcePath: filename, query: options, async: markAsync }, source)
14
+ if (isAsync) {
15
+ return new Promise((resolve, reject) => {
16
+ resolveAsync = resolve
17
+ rejectAsync = reject
18
+ })
19
+ } else {
20
+ return result
21
+ }
22
+ }
@@ -0,0 +1,86 @@
1
+ // ref: https://github.com/kristerkari/react-native-css-transformer
2
+ const css2rn = require('@startupjs/css-to-react-native-transform').default
3
+
4
+ const EXPORT_REGEX = /:export\s*\{/
5
+
6
+ module.exports = function cssToReactNative (source) {
7
+ source = escapeExport(source)
8
+ const cssObject = css2rn(source, { parseMediaQueries: true, parsePartSelectors: true })
9
+ for (const key in cssObject.__exportProps || {}) {
10
+ cssObject[key] = parseStylValue(cssObject.__exportProps[key])
11
+ }
12
+ // save hash to use with the caching system of @startupjs/cache
13
+ cssObject.__hash__ = simpleNumericHash(JSON.stringify(cssObject))
14
+ return 'module.exports = ' + JSON.stringify(cssObject)
15
+ }
16
+
17
+ function parseStylValue (value) {
18
+ if (typeof value !== 'string') return value
19
+ // strip single quotes (stylus adds it for the topmost value)
20
+ // and parens (stylus adds them for values in a hash)
21
+ // Instead of doing a simple regex replace for both beginning and end,
22
+ // we only find beginning chars and then cut string from both sides.
23
+ // This is needed to prevent false-replacing the paren at the end of
24
+ // values like 'rgba(...)'
25
+ if (/^['"(]/.test(value)) {
26
+ const wrapsLength = value.match(/^['"(]+/)[0].length
27
+ value = value.slice(wrapsLength).slice(0, -wrapsLength)
28
+ }
29
+ // hash
30
+ if (value.charAt(0) === '{') {
31
+ const hash = JSON.parse(value)
32
+ for (const key in hash) {
33
+ hash[key] = parseStylValue(hash[key])
34
+ }
35
+ return hash
36
+ } else if (!isNaN(parseFloat(value))) {
37
+ return parseFloat(value)
38
+ } else {
39
+ return value
40
+ }
41
+ }
42
+
43
+ // Process :export properties by wrapping their values in quotes
44
+ function escapeExport (source) {
45
+ const match = source.match(EXPORT_REGEX)
46
+ if (!match) return source
47
+
48
+ // 1. find closing bracket of :export { ... }
49
+ const matchIndex = match.index
50
+ const matchStr = match[0]
51
+ const matchLength = matchStr.length
52
+ const start = matchIndex + matchLength
53
+ let openBr = 1 // Count opened brackets, we start from one already opened
54
+ let end
55
+
56
+ for (let i = start; i < source.length; i++) {
57
+ if (source.charAt(i) === '}') {
58
+ --openBr
59
+ } else if (source.charAt(i) === '{') {
60
+ ++openBr
61
+ }
62
+
63
+ if (openBr <= 0) {
64
+ end = i
65
+ break
66
+ }
67
+ }
68
+ if (!end) return source
69
+
70
+ // 2. escape all exported values
71
+ const properties = source
72
+ .slice(start, end)
73
+ .split(';')
74
+ .map(line => line.replace(/(:\s+)([^'"].*[^'"])$/, '$1\'$2\''))
75
+ .join(';')
76
+
77
+ source = source.slice(0, start) + properties + source.slice(end)
78
+
79
+ return source
80
+ }
81
+
82
+ // ref: https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0?permalink_comment_id=2694461#gistcomment-2694461
83
+ function simpleNumericHash (s) {
84
+ for (var i = 0, h = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0
85
+ return h
86
+ }
@@ -0,0 +1,18 @@
1
+ // TODO: add support for source maps
2
+ const babel = require('@babel/core')
3
+
4
+ module.exports = function cssxjsBabelLoader (source) {
5
+ const filename = this.resourcePath
6
+ const { platform } = this.query
7
+
8
+ return babel.transformSync(source, {
9
+ filename,
10
+ babelrc: false,
11
+ configFile: false,
12
+ plugins: [
13
+ [require('babel-preset-cssxjs'), {
14
+ platform
15
+ }]
16
+ ]
17
+ }).code
18
+ }
@@ -0,0 +1,66 @@
1
+ // ref: https://github.com/kristerkari/react-native-stylus-transformer
2
+ const { existsSync } = require('fs')
3
+ const { join } = require('path')
4
+ const stylus = require('stylus')
5
+
6
+ const PROJECT_STYLES_PATH = join(process.cwd(), 'styles/index.styl')
7
+ let UI_STYLES_PATH
8
+
9
+ function renderToCSS (src, filename, { platform } = {}) {
10
+ const compiler = stylus(src)
11
+ compiler.set('filename', filename)
12
+
13
+ if (platform) {
14
+ compiler.define('$PLATFORM', platform)
15
+ compiler.define(`__${platform.toUpperCase()}__`, true)
16
+ }
17
+
18
+ if (checkUiStylesExist()) {
19
+ compiler.import(UI_STYLES_PATH)
20
+ }
21
+
22
+ // TODO: Make this a setting
23
+ if (checkProjectStylesExist()) {
24
+ compiler.import(PROJECT_STYLES_PATH)
25
+ }
26
+
27
+ let compiled
28
+ compiler.render(function (err, res) {
29
+ if (err) {
30
+ throw new Error(err)
31
+ }
32
+ compiled = res
33
+ })
34
+
35
+ return compiled
36
+ }
37
+
38
+ module.exports = function stylusToReactNative (source) {
39
+ return renderToCSS(source, this.resourcePath, this.query)
40
+ }
41
+
42
+ // check if @startupjs/ui is being used to load styles file from it, cache result for 5 seconds
43
+ let uiStylesExist
44
+ let uiStylesLastChecked = 0
45
+ function checkUiStylesExist () {
46
+ if (uiStylesLastChecked + 5000 > Date.now()) return uiStylesExist
47
+ uiStylesLastChecked = Date.now()
48
+ try {
49
+ // TODO: make this configurable
50
+ UI_STYLES_PATH = join(require.resolve('@startupjs/ui'), '../styles/index.styl')
51
+ uiStylesExist = existsSync(UI_STYLES_PATH)
52
+ } catch {
53
+ uiStylesExist = false
54
+ }
55
+ return uiStylesExist
56
+ }
57
+
58
+ // check if project styles file exist, cache result for 5 seconds
59
+ let projectStylesExist
60
+ let projectStylesLastChecked = 0
61
+ function checkProjectStylesExist () {
62
+ if (projectStylesLastChecked + 5000 > Date.now()) return projectStylesExist
63
+ projectStylesLastChecked = Date.now()
64
+ projectStylesExist = existsSync(PROJECT_STYLES_PATH)
65
+ return projectStylesExist
66
+ }
@@ -0,0 +1,42 @@
1
+ const stylusToCssLoader = require('./lib/stylusToCssLoader')
2
+ const cssToReactNativeLoader = require('./lib/cssToReactNativeLoader')
3
+ const cssxjsBabelLoader = require('./lib/cssxjsBabelLoader')
4
+ const callLoader = require('./lib/callLoader')
5
+
6
+ module.exports.transform = async function cssxjsMetroBabelTransform ({
7
+ src, filename, options: { upstreamTransformer, ...options } = {}
8
+ }) {
9
+ upstreamTransformer ??= getUpstreamTransformer()
10
+ const { platform } = options
11
+
12
+ // from exotic extensions to js
13
+ if (/\.styl$/.test(filename)) {
14
+ // TODO: Refactor `platform` to be just passed externally as an option in metro and in webpack
15
+ src = callLoader(stylusToCssLoader, src, filename, { platform })
16
+ src = callLoader(cssToReactNativeLoader, src, filename)
17
+ } else if (/\.css$/.test(filename)) {
18
+ src = callLoader(cssToReactNativeLoader, src, filename)
19
+ }
20
+
21
+ // js transformations
22
+ if (/\.[mc]?[jt]sx?$/.test(filename)) {
23
+ src = callLoader(cssxjsBabelLoader, src, filename, { platform })
24
+ }
25
+
26
+ return upstreamTransformer.transform({ src, filename, options })
27
+ }
28
+
29
+ function getUpstreamTransformer () {
30
+ try {
31
+ // Expo
32
+ return require('@expo/metro-config/babel-transformer')
33
+ } catch (err) {
34
+ try {
35
+ // React Native 0.73+
36
+ return require('@react-native/metro-babel-transformer')
37
+ } catch (err) {
38
+ // React Native <0.73
39
+ return require('metro-react-native-babel-transformer')
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,39 @@
1
+ // To pass existing config for modification, pass it as 'upstreamConfig' in options:
2
+ // config = getDefaultConfig(__dirname, { upstreamConfig })
3
+ exports.getDefaultConfig = function getDefaultConfig (projectRoot, { upstreamConfig } = {}) {
4
+ upstreamConfig ??= getUpstreamConfig(projectRoot)
5
+
6
+ const config = {
7
+ ...upstreamConfig,
8
+ transformer: {
9
+ ...upstreamConfig.transformer,
10
+ babelTransformerPath: require.resolve('./metro-babel-transformer.js'),
11
+ unstable_allowRequireContext: true
12
+ },
13
+ resolver: {
14
+ ...upstreamConfig.resolver,
15
+ sourceExts: [...new Set([
16
+ ...(upstreamConfig.resolver.sourceExts || []),
17
+ ...['css', 'styl']
18
+ ])],
19
+ unstable_enablePackageExports: true
20
+ }
21
+ }
22
+
23
+ return config
24
+ }
25
+
26
+ function getUpstreamConfig (projectRoot) {
27
+ try {
28
+ // Expo
29
+ return require('expo/metro-config').getDefaultConfig(projectRoot)
30
+ } catch (err) {
31
+ try {
32
+ // React Native 0.73+
33
+ return require('@react-native/metro-config').getDefaultConfig(projectRoot)
34
+ } catch (err) {
35
+ // React Native <0.73
36
+ return require('metro-config').getDefaultConfig()
37
+ }
38
+ }
39
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@cssxjs/bundler",
3
+ "version": "0.2.0",
4
+ "description": "Compile CSSX styles in React Native and Web bundlers",
5
+ "exports": {
6
+ "./metro-config": "./metro-config.js",
7
+ "./metro-babel-transformer": "./metro-babel-transformer.js",
8
+ "./lib/callLoader": "./lib/callLoader.js",
9
+ "./lib/stylusToCssLoader": "./lib/stylusToCssLoader.js",
10
+ "./lib/cssToReactNativeLoader": "./lib/cssToReactNativeLoader.js"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "scripts": {
16
+ "test": "echo 'No tests yet' && exit 0"
17
+ },
18
+ "author": {
19
+ "name": "Pavel Zhukov",
20
+ "email": "cray0000@gmail.com"
21
+ },
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@babel/core": "^7.0.0",
25
+ "@cssxjs/babel-plugin-rn-stylename-inline": "^0.2.0",
26
+ "@startupjs/css-to-react-native-transform": "^2.1.0-0",
27
+ "babel-preset-cssxjs": "^0.2.0",
28
+ "stylus": "0.54.7"
29
+ },
30
+ "gitHead": "a80a44b4d14c116871ca03997ce772b6beabf5d8"
31
+ }