Skip to content

Commit 159dd6b

Browse files
Merge pull request #56 from viivue/atomic-1.2.5
Atomic CSS v1.2.5
2 parents 8a664ea + 5ead2c1 commit 159dd6b

30 files changed

Lines changed: 1008 additions & 657 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/.idea/
22
/package-lock.json
3+
/pnpm-lock.yaml
34
/node_modules/

.npmignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
.idea
22
.gitignore
3-
/css
4-
/scss
5-
/dist/atomic.css
3+
/css

README.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,59 @@ Or, you can download the default Atomic CSS files in the [`/dist` folder](https:
3838
Check the CDN served by jsDelivr [here](https://www.jsdelivr.com/package/gh/viivue/atomic-css?tab=files&path=dist)
3939

4040
```html
41-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/viivue/atomic-css@1.1.10/dist/atomic.min.css">
41+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/viivue/atomic-css@1.2.5/dist/atomic.min.css">
4242
```
4343

4444
### Customization
4545

46-
To add custom classes for a specific project, you will have to:
46+
#### Option 1 — CLI (recommended)
4747

48-
1. Clone this repo to your local machine.
49-
2. Edit the `/scss/_config.scss`, you will find some example templates there.
50-
3. Generate the new Atomic CSS by `npm run prod`.
48+
Build a custom Atomic CSS directly from your project without cloning this repo.
49+
50+
**1. Install the package**
51+
52+
```shell
53+
npm i @viivue/atomic-css
54+
```
55+
56+
**2. Copy `_config.scss` into your project**
57+
58+
Download the config file directly from GitHub — it contains commented-out examples for all available variables (colors, fonts, breakpoints, etc.):
59+
60+
```shell
61+
curl -o _config.scss https://raw.githubusercontent.com/viivue/atomic-css/main/scss/_config.scss
62+
```
63+
64+
Or [view it on GitHub](https://github.com/viivue/atomic-css/blob/main/scss/_config.scss) and copy it manually.
65+
66+
Uncomment and edit only the variables you want to override; everything else falls back to the built-in defaults automatically.
67+
68+
**3. Add a build script to your `package.json`**
69+
70+
```json
71+
{
72+
"scripts": {
73+
"build:css": "atomic-css --config scss/_config.scss --output dist/"
74+
}
75+
}
76+
```
77+
78+
**4. Run**
79+
80+
```shell
81+
npm run build:css
82+
```
83+
84+
This generates `atomic.css` and `atomic.min.css` in the output folder.
85+
86+
#### Option 2 — Clone & edit
87+
88+
For contributors or anyone who wants to fork and modify the framework itself.
89+
90+
1. Clone this repository to your local machine.
91+
2. Run `npm install` to install dependencies.
92+
3. Edit `/scss/_config.scss` to override variables (colors, fonts, breakpoints, etc.).
93+
4. Run `npm run prod` to compile the output CSS.
5194

5295
## Deployment
5396

bin/build.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* @viivue/atomic-css — build script
5+
*
6+
* How it works:
7+
* 1. Copies this package's scss/ folder to a unique temp directory.
8+
* 2. Overwrites _config.scss in that temp directory with your project's config.
9+
* 3. Compiles from the temp directory — node_modules is never modified.
10+
* 4. Writes atomic.css and atomic.min.css to your output folder (--output).
11+
* 5. Cleans up the temp directory in the finally block.
12+
*
13+
* Usage (in your project's package.json scripts):
14+
* node node_modules/@viivue/atomic-css/bin/build.js \
15+
* --config path/to/your/_config.scss \
16+
* --output path/to/your/output/folder
17+
*/
18+
19+
'use strict';
20+
21+
const os = require('os');
22+
const path = require('path');
23+
const fs = require('fs');
24+
const sass = require('sass');
25+
const csso = require('csso');
26+
27+
// ── Read CLI arguments ────────────────────────────────────────────────────────
28+
const args = process.argv.slice(2);
29+
30+
function getArg(name){
31+
const i = args.indexOf(name);
32+
const val = i !== -1 ? args[i + 1] : undefined;
33+
// Reject if the "value" is itself a flag (e.g. --config --output ./out)
34+
return val && !val.startsWith('--') ? val : null;
35+
}
36+
37+
const configArg = getArg('--config');
38+
const outputArg = getArg('--output');
39+
40+
// Both --config and --output are required.
41+
// If called with no args at all (e.g. checking the binary exists), exit silently.
42+
// If only one arg is missing, tell the user which one and exit with code 1
43+
// so npm scripts and CI pipelines correctly detect the failure.
44+
if(!configArg || !outputArg){
45+
if(args.length === 0) process.exit(0);
46+
if(!configArg) console.error('\n [atomic-css] Missing required argument: --config <path/to/_config.scss>\n');
47+
if(!outputArg) console.error('\n [atomic-css] Missing required argument: --output <path/to/output/>\n');
48+
process.exit(1);
49+
}
50+
51+
const configPath = path.resolve(process.cwd(), configArg);
52+
const outputDir = path.resolve(process.cwd(), outputArg);
53+
54+
if(!fs.existsSync(configPath)){
55+
console.error(`[atomic-css] Config not found: ${configPath}`);
56+
process.exit(1);
57+
}
58+
59+
// ── Paths inside this package ─────────────────────────────────────────────────
60+
// __dirname is the bin/ folder of this package (inside node_modules)
61+
const pkgDir = path.join(__dirname, '..');
62+
const configDest = path.join(pkgDir, 'scss', '_config.scss'); // checked for package integrity only
63+
const buildEntry = path.join(pkgDir, 'scss', '_build.scss'); // main sass entry point
64+
65+
// ── Helpers ───────────────────────────────────────────────────────────────────
66+
67+
// Read _build.scss and return the list of module names it imports
68+
function getModules(buildFile){
69+
const content = fs.readFileSync(buildFile, 'utf8');
70+
return [...content.matchAll(/@use\s+["']([^"']+)["']/g)].map(m => m[1]);
71+
}
72+
73+
// Extract a short module name from a sass error/warning URL for readable logs
74+
function moduleFromUrl(url){
75+
if(!url) return 'unknown';
76+
const file = url.pathname || url.toString();
77+
return path.basename(file, '.scss').replace(/^_/, '');
78+
}
79+
80+
// ── Build ─────────────────────────────────────────────────────────────────────
81+
82+
// tempDir is declared outside try so finally can always clean it up.
83+
let tempDir = null;
84+
85+
// buildFailed is set in catch so we can call process.exit(1) AFTER finally
86+
// has cleaned up the temp directory.
87+
// Never call process.exit() inside catch — it skips the finally block entirely.
88+
let buildFailed = false;
89+
90+
try{
91+
// Verify package integrity before doing anything
92+
if(!fs.existsSync(configDest) || !fs.existsSync(buildEntry)){
93+
console.error(`[atomic-css] Package files missing in: ${path.join(pkgDir, 'scss')}`);
94+
console.error(' Try reinstalling @viivue/atomic-css.');
95+
process.exit(1);
96+
}
97+
98+
// Copy the entire scss/ folder to a unique temp directory so node_modules
99+
// is never modified — safe for read-only environments and concurrent builds.
100+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'atomic-css-'));
101+
fs.cpSync(path.join(pkgDir, 'scss'), tempDir, {recursive: true});
102+
103+
// Overwrite _config.scss in the temp directory with the user's config
104+
fs.copyFileSync(configPath, path.join(tempDir, '_config.scss'));
105+
106+
const tempBuildEntry = path.join(tempDir, '_build.scss');
107+
const modules = getModules(tempBuildEntry);
108+
109+
// Create the output folder if it doesn't exist yet
110+
fs.mkdirSync(outputDir, {recursive: true});
111+
112+
const warnings = [];
113+
114+
const {css} = sass.compile(tempBuildEntry, {
115+
sourceMap: false,
116+
// Allow sass to resolve package imports (e.g. @use "@viivue/atomic-css/...")
117+
// from the project's node_modules folder
118+
loadPaths: [path.join(process.cwd(), 'node_modules')],
119+
logger: {
120+
warn(message, {span}){
121+
const mod = span?.url ? moduleFromUrl(span.url) : 'unknown';
122+
warnings.push(` WARN [${mod}] ${message}`);
123+
},
124+
debug(){
125+
},
126+
},
127+
});
128+
129+
// Print each compiled module so the user knows what ran
130+
modules.forEach(m => console.log(` OK ${m}`));
131+
132+
if(warnings.length){
133+
console.log('');
134+
warnings.forEach(w => console.warn(w));
135+
}
136+
137+
// Minify and write both the readable and minified versions
138+
const {css: minified} = csso.minify(css);
139+
140+
const cssOutPath = path.join(outputDir, 'atomic.css');
141+
const minOutPath = path.join(outputDir, 'atomic.min.css');
142+
fs.writeFileSync(cssOutPath, css);
143+
fs.writeFileSync(minOutPath, minified);
144+
145+
function formatBytes(n){
146+
return n >= 1024 ? (n / 1024).toFixed(1) + ' kB' : n + ' B';
147+
}
148+
149+
const cssSize = formatBytes(Buffer.byteLength(css));
150+
const minSize = formatBytes(Buffer.byteLength(minified));
151+
console.log('');
152+
console.log(` ✓ atomic.css ${cssSize.padStart(8)}${outputDir}`);
153+
console.log(` ✓ atomic.min.css ${minSize.padStart(8)}${outputDir}`);
154+
155+
}catch(err){
156+
const mod = err.span?.url ? moduleFromUrl(err.span.url) : 'unknown';
157+
// span.start.line is 0-based per the Sass JS API spec, so add 1 for display
158+
const line = err.span?.start?.line != null ? `:${err.span.start.line + 1}` : '';
159+
console.error('');
160+
console.error(` ERR [${mod}${line}] ${err.sassMessage || err.message}`);
161+
buildFailed = true;
162+
163+
}finally{
164+
// Clean up the temp directory whether the build succeeded or failed.
165+
if(tempDir){
166+
try{
167+
fs.rmSync(tempDir, {recursive: true, force: true});
168+
}catch(cleanupErr){
169+
console.error(` [atomic-css] WARNING: Could not clean up temp directory: ${cleanupErr.message}`);
170+
}
171+
}
172+
}
173+
174+
// Exit after finally has cleaned up the temp directory.
175+
if(buildFailed) process.exit(1);

0 commit comments

Comments
 (0)