@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 +14 -0
- package/README.md +53 -0
- package/lib/callLoader.js +22 -0
- package/lib/cssToReactNativeLoader.js +86 -0
- package/lib/cssxjsBabelLoader.js +18 -0
- package/lib/stylusToCssLoader.js +66 -0
- package/metro-babel-transformer.js +42 -0
- package/metro-config.js +39 -0
- package/package.json +31 -0
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
|
+
}
|
package/metro-config.js
ADDED
|
@@ -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
|
+
}
|