Bootcamp
A guided 60-minute bootcamp that converts a legacy SCSS stylesheet to modern modules, introduces a token map, and ships a Stylelint config that prevents drift. Run it on a real branch in an hour.
A 60-minute SCSS modernisation bootcamp
EXAMPLE
# ===== Bootcamp objectives =====
# 1. Replace @import with @use / @forward
# 2. Introduce a token map and a token() function
# 3. Add a respond-to mixin from a breakpoints map
# 4. Lock in conventions with Stylelint + a CI rule
# ===== 0-5 min: pick the target stylesheet =====
# One feature folder. Counts < 1000 lines so you finish in the hour.
# Open the entry point styles.scss (or app.scss) in VS Code.
# ===== 5-20 min: migrate @import -> @use =====
npm i -g sass-migrator
sass-migrator module --migrate-deps styles.scss
git diff # review
# Things to fix manually
# - @use 'tokens' as t; variables become t.$primary
# - Global functions are now sass:color, sass:math, sass:list, sass:map
# - Configuration of imported modules uses 'with ($primary: red)'
# ===== 20-35 min: introduce design tokens as data =====
# Create _tokens.scss
cat > _tokens.scss <<'EOF'
@use 'sass:map';
$tokens: (
'color': (
'brand': (500: #2563eb, 600: #1e40af),
'gray': (50: #f8fafc, 900: #0f172a),
),
'space': ('xs': .25rem, 'sm': .5rem, 'md': 1rem, 'lg': 2rem, 'xl': 4rem),
'radius': ('sm': 4px, 'md': 8px, 'lg': 16px, 'pill': 999px),
);
@function token($path...) {
$v: $tokens;
@each $k in $path { $v: map.get($v, $k); }
@if $v == null { @error 'Unknown token: #{$path}'; }
@return $v;
}
EOF
# Replace one-off hex codes / pixel values with token() calls in the migrated file.
# Aim for 80% replaced; the last 20% are usually genuine one-offs.
# ===== 35-50 min: add a responsive breakpoint mixin =====
cat >> _tokens.scss <<'EOF'
$bp: ('sm': 480px, 'md': 768px, 'lg': 1024px, 'xl': 1280px);
@mixin respond-to($name) {
@if not map.has-key($bp, $name) { @error 'Unknown bp #{$name}'; }
@media (min-width: map.get($bp, $name)) { @content; }
}
EOF
# Replace ad-hoc @media (min-width: 768px) blocks with @include respond-to('md').
# ===== 50-60 min: enforce the new style with Stylelint =====
npm i -D stylelint stylelint-config-standard-scss stylelint-config-recess-order
# .stylelintrc.json
{
"extends": ["stylelint-config-standard-scss", "stylelint-config-recess-order"],
"rules": {
"at-rule-disallowed-list": ["import"], // ban legacy @import
"scss/at-import-no-partial-leading-underscore": true,
"scss/dollar-variable-pattern": "^[a-z][a-zA-Z0-9]+$",
"declaration-block-no-redundant-longhand-properties": null,
"unit-allowed-list": ["px", "rem", "%", "em", "s", "ms", "deg", "vh", "vw"],
"color-no-hex": [true, { "message": "Use token() instead of literal hex." }]
},
"overrides": [{ "files": ["_tokens.scss"], "rules": { "color-no-hex": null } }]
}
# Add to package.json
# {
# "scripts": {
# "lint:scss": "stylelint 'resources/scss/**/*.scss'",
# "lint:scss:fix": "stylelint --fix 'resources/scss/**/*.scss'"
# }
# }
# CI step
# - name: stylelint
# run: npm run lint:scss
# ===== Post-bootcamp =====
# - Open a PR titled 'SCSS migration to modules + tokens'
# - Write the conventions in CONTRIBUTING.md so the next contributor follows them
# - Plan a follow-up to migrate another feature folder next week
# ===== Common surprises during migration =====
# - Variables that LOOK global were leaking across files; @use scope makes
# the dependency explicit and refactors easier
# - Division ($x / 2) now warns -> rewrite as 'math.div($x, 2)' or '$x * .5'
# - 'darken'/'lighten' deprecation -> use color.adjust($c, $lightness: -10%)
Why it matters
Run sass-migrator first, before any hand edits. It rewrites 90% of the migration mechanically and surfaces the few hand-fix cases in a clean diff — the alternative is a week of slow conversion where every commit risks visual regressions because the SCSS module boundary changes in subtle ways.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…