| 开发者 | simonmista |
|---|---|
| 更新时间 | 2026年8月22日 19:08 |
| PHP版本: | 8.1 及以上 |
| WordPress版本: | 7.1 |
| 版权: | GPLv2 or later |
| 版权网址: | 版权信息 |
.scss file. Your site needs a .css file. Theme SCSS Compiler does that one conversion, on your server, from Tools → Theme SCSS Compiler.
You give it a source and a target, for example assets/scss/style.scss and assets/css/style.css. Save the SCSS, open any admin page, and the CSS is rebuilt. No Node.js, no npm install, no Gulp task, no CI step: the Sass compiler (scssphp) ships inside the plugin. SCSS is the Sass syntax that looks like CSS. Every valid CSS file is already valid SCSS, and you get nesting, variables, mixins and @import on top.
It is built for the case that keeps coming up: a theme with one or two stylesheets, hosting where a build pipeline is not going to happen, and someone who wants to change a variable and see the result.
Features
style_loader_src filter.wp-config.php, a theme, or Bedrock... is refused, and nothing is ever written outside the theme. An @import inside your SCSS can still read a file elsewhere on the server, and output built from one is refused before it reaches disk. With a child theme active, the child is that theme.
That keeps the tool small on purpose. One theme, its stylesheets, nothing else to configure.
Where the compiled CSS goes
Compiling writes into your theme. It writes the CSS target of each pair, plus a short-lived temporary file beside it that is renamed onto the target when the compile finishes, and it creates the directory for the target if it is missing. Nothing else in the theme is touched: sources are only read, and a target that is not a .css file is refused before the write, so a mistyped path cannot land on functions.php.
Commit those CSS files or add them to .gitignore. Both work, because auto-compile rebuilds anything missing.
Privacy
No external HTTP requests, no cookies, no telemetry, no tracking. Everything happens on your server. The plugin bundles scssphp and its dependencies (league/uri, scssphp/source-span, symfony/filesystem, the symfony ctype and mbstring polyfills, and the PSR HTTP interfaces). All are MIT licensed and GPL-compatible, and their source sits in the plugin's vendor/ directory.
theme-scss-compiler folder to /wp-content/plugins/, or install the ZIP through Plugins → Add New.assets/scss/style.scss, replace the default pair in step 3 with your own paths. Auto-compile skips a pair whose source does not exist; Compile now reports the missing source.
Configuration can also live in code. See the FAQ entries on TSCSSCOMPILER_PAIRS.No. The compiler is PHP and ships with the plugin. Nothing is installed on the server, nothing runs on your machine. You need WordPress 6.3 and PHP 8.1 or newer.
Everything you reach for in a theme stylesheet: nesting, the & parent selector, variables, mixins, functions, @extend, @media nesting, arithmetic, interpolation, and @import for splitting your styles into partials. Import chains nest as deep as you like, and every file in the chain is tracked for change detection.
Partials are the @import kind, so _variables.scss is pulled in with @import "variables"; at the top of your entry file, and everything it declares is available below. The compiler is a PHP port of Sass rather than Dart Sass, so it is worth a look at the compiled output the first time you move an existing theme over.
Give a partial a leading underscore and pull it in with @import at the top of your entry file. Everything a partial declares is available to everything below it.
// assets/scss/_variables.scss
$brand: #0073aa;
$text: #1f232b;
$radius: 8px;
// assets/scss/_card.scss
.card {
color: $text;
border-radius: $radius;
}
// assets/scss/_menu.scss
.menu a {
color: $brand;
}
// assets/scss/style.scss — the file you set as the pair's SCSS source
@import "variables";
@import "card";
@import "menu";
Note the import name drops both the underscore and the extension: the file _card.scss is imported as "card".
Two things follow from this, and both are easier to get right if you know them up front.
Order matters. A partial is read at the position where you import it, so @import "variables" has to come before the partials that use those variables. Variables first, the rest below.
One shared scope. Every partial writes into the same namespace, so two partials that each declare $padding will quietly disagree — last one wins. Prefix instead: $card-padding, $menu-padding. Same for mixin names.
When the list gets long, collect it in one index partial and import just that:
// assets/scss/_index.scss
@import "variables";
@import "card";
@import "menu";
// assets/scss/style.scss
@import "index";
Chains nest as deep as you like. The plugin records every file that was read at any depth, so editing _card.scss two levels down still triggers the rebuild on your next admin page load.
Every valid CSS file is already valid SCSS, so you can rename style.css to style.scss and start there.
Nesting. Write child selectors inside the parent instead of repeating it:
.card {
padding: 1rem;
a { color: rebeccapurple; }
}
compiles to .card { padding: 1rem; } and .card a { color: rebeccapurple; }.
The & parent reference, useful for states:
.button {
background: #0073aa;
&:hover { background: #005177; }
&.active { background: #003f66; }
}
compiles to .button, .button:hover and .button.active with those three colours.
Variables. Set a value once and reuse it:
$brand: #0073aa;
a { color: $brand; }
.button { background: $brand; }
One thing that surprises people: the default output style is compressed, so the compiled file is one long line and rebeccapurple has become #639. Set the compiler to Expanded while you are learning. Full language guide: https://sass-lang.com/guide/
Yes, one kind of file. It is a compiler, so the boundary matters more than a reassuring "no".
It writes the CSS target of each pair, for example assets/css/style.css. That file is replaced in full on every compile, so treat it as build output and never hand-edit it. It also creates the directory for that file when the directory does not exist.
Each compile briefly writes a second file next to the target, style.css.tscsscompiler-tmp, and renames it onto the target when it is finished — that is what makes the swap atomic. It is gone by the time the compile returns. The name deliberately does not end in .css, so if a process is killed part-way the leftover is never served as a stylesheet; you can delete it.
It writes nothing else. Your SCSS is only read. functions.php, templates and every other non-.css file are unreachable, because a target without a .css extension is refused before the write. Nothing outside the active theme can be written either: .. is rejected, and a target that turns out to be a symlink pointing out of the theme is rejected too.
Cache-busting versions are never written into a file. They go onto the stylesheet URL at request time through the style_loader_src filter, so your theme's own wp_enqueue_style() calls stay as they are.
One thing to watch: the target does not have to be a new file. Point a pair at a .css file that already exists in your theme and that file is replaced.
No, that one target is refused. style.css is where WordPress reads Theme Name: from, and a compile replaces the whole file, so one typo there would leave the theme unrecognisable and drop it out of Appearance → Themes. The plugin says so instead of writing.
Compile to a separate file and enqueue it alongside your style.css:
assets/scss/style.scss → assets/css/style.css
Any other name works too. Only the theme's own root style.css is off limits; a style.css in a subfolder is fine.
Only when that pair's freshly compiled CSS differs from the file already on disk. Other pairs keep their version.
An edit that produces identical CSS does not move the version. That covers // comments in any mode, and /* */ comments in compressed output, because those are stripped. In expanded output a /* */ comment is part of the CSS, so editing one is a real change and does bump the version.
Versions are three-part numbers and the patch digit advances: 1.0.4 becomes 1.0.5.
Two separate settings, with different defaults. Auto-compile is on by default. On every wp-admin page load it checks whether any compiled CSS is missing or older than its SCSS source or tracked partials, and rebuilds what needs it. Nothing runs on a schedule; your own page load triggers it. Auto-recompile is off by default. Turn it on and the settings page runs a countdown, from 1 to 999 seconds, and re-checks on each tick while that page stays open in your browser. It counts down only while that tab is the one you are looking at, and it holds off while the form has unsaved changes; the readout says which of the two is happening. It compiles only when something actually changed, so a page left open does not keep rewriting the CSS. They are independent. Switching Auto-compile off does not stop the timer. To compile only on the button, switch both off.
In this order:
TSCSSCOMPILER_PAIRS, versions are never rewritten. Edit the version value in your code.Each target is written inside the active theme at the path you configured. With Auto-compile on, the next wp-admin page load notices a missing file and builds it, so a deploy or a git pull without compiled CSS does not leave the site unstyled. Nothing rebuilds until someone with access opens an admin page, though, and the pair is not enqueued in the meantime. Deploying to production without visiting wp-admin means you should commit the compiled CSS.
@import-ed partials?Yes. After each successful compile the plugin stores every file the compiler read, including nested import chains, and Auto-compile compares each of them against the compiled CSS. So if style.scss does @import "menu"; and you only edit _menu.scss, the next admin page load recompiles. You never have to touch the entry file.
No. Compiling happens only in wp-admin, for a logged-in user who passes the capability check, and it is triggered in exactly three places: an admin page load with Auto-compile on, the Compile now button, and the settings-page timer. The last two are admin-ajax requests, so also wp-admin. There is no WP-Cron job and no scheduled task. Visitors are served the already compiled file and never cause any Sass work.
On Tools → Theme SCSS Compiler. A failed compile is kept in a "Last compile error" panel with server paths stripped from the message, and a successful compile clears it. The stored error also expires by itself after 24 hours. Compile now reports success or failure right away. There is no notice on other admin screens, so a failed auto-compile is visible only on the plugin's own page.
Each pair has one. Frontend pairs are enqueued on wp_enqueue_scripts, so on the public site only. Admin pairs are enqueued on admin_enqueue_scripts, so in wp-admin only. The version filter applies in both. New pairs start as Frontend.
Yes, and the active child theme is the boundary. Paths resolve against the active stylesheet directory, which is the child when one is active. That is also the limit: a path containing .. is refused, and while an @import can read a file in the parent theme, the compile is stopped before anything is written. If the SCSS you want to compile lives in the parent theme, configure it while the parent is active, or copy the sources into the child.
No. Both paths resolve against the active theme, a path containing .. is rejected, the target must end in .css, and a symlinked target pointing out of the theme is refused. Writes cannot leave the theme.
Reads are a different matter. An @import in your SCSS can name a file anywhere the PHP process can read. That file is read and parsed, and only then does the plugin refuse to write output built from it. The contents never reach a public CSS file, but anyone who can edit the SCSS can make the compiler open a file elsewhere on the server. Treat access to this plugin the way you treat access to the theme editor.
Deactivating stops the enqueuing. If the plugin was enqueueing your stylesheets, they are gone from the page until you reactivate it or add wp_enqueue_style() to your theme. The compiled CSS files stay on disk.
Deleting removes the plugin's options and transients, on every site of a multisite network. It does not delete the compiled CSS in your theme. Those files are yours and remain.
functions.php?Either. Auto-enqueue is on by default: the plugin calls wp_enqueue_style() for each pair on that pair's context, as late as the hook allows, after everything else has registered, and skips a file whose URL is already registered so it does not load twice. That check is a URL match at the moment the hook runs, so a stylesheet registered later, or registered under a differently written URL such as http:// against https://, can slip past it.
Enqueueing in functions.php is more predictable, and it means the front end does not depend on this plugin staying active. Switch Auto-enqueue off and enqueue the compiled file yourself. The version filter keeps working either way.
Administrators only, through the manage_options capability. The menu entry, the save handler, both AJAX endpoints and the auto-compile hook all check it, so Editors and Authors neither see nor reach it. To open it up, filter the capability:
add_filter( 'tscsscompiler_capability', static function () {
return 'edit_theme_options';
} );
Do that carefully. Whoever passes the check can set the file pairs, write CSS into the active theme, publish it on the front end, and make the compiler read any file the web server can read. It is a code-adjacent permission, so keep it with roles you already trust with the theme.
Yes, one setting at a time. Define the constant for the setting you care about and it wins over whatever the database holds. You do not have to move the rest of your configuration into code with it. // Enough on its own. The database keeps every other setting. define( 'TSCSSCOMPILER_OUTPUT_STYLE', 'expanded' ); A setting a constant supplies is shown on the settings page but its control is disabled, and the page names the constants in play so you know where to look. That is deliberate: an editable control would let you store a value that the constant overrides on the very next page load. Here is the full set. Where a constant is absent, the value after the arrow is the fallback the database starts from.
TSCSSCOMPILER_AUTO_COMPILE, true / false → trueTSCSSCOMPILER_AUTO_ENQUEUE, true / false → trueTSCSSCOMPILER_OUTPUT_STYLE, 'compressed' / 'expanded' → 'compressed'TSCSSCOMPILER_AUTO_RECOMPILE, true / false → falseTSCSSCOMPILER_AUTO_COMPILE_INTERVAL, seconds from 1 to 999 → 30TSCSSCOMPILER_BUMP_VERSION, true / false → truescss_path, css_path, version (three-part, or it falls back to 1.0.0) and context (frontend or admin, anything else becomes frontend).
The consequence: with the pairs in code the version numbers are in code too, so nothing can bump them. Version bumping is switched off and locked while TSCSSCOMPILER_PAIRS is defined, whatever TSCSSCOMPILER_BUMP_VERSION says. Edit the version value yourself when you want the URL to change.
Whatever the constants override stays untouched in the database, so removing a define() again gives you back the value you had before.
define() calls?wp-config.php is the simplest place, and it loads before any plugin:
define( 'TSCSSCOMPILER_PAIRS', [
[ 'scss_path' => 'assets/scss/style.scss', 'css_path' => 'assets/css/style.css', 'version' => '1.0.0', 'context' => 'frontend' ],
] );
define( 'TSCSSCOMPILER_OUTPUT_STYLE', 'compressed' );
Your theme's functions.php works as well. Hook after_setup_theme; the plugin does not read its settings before then:
add_action( 'after_setup_theme', static function () {
if ( ! defined( 'TSCSSCOMPILER_PAIRS' ) ) {
define( 'TSCSSCOMPILER_PAIRS', [
[ 'scss_path' => 'assets/scss/style.scss', 'css_path' => 'assets/css/style.css', 'version' => '1.0.0', 'context' => 'frontend' ],
] );
}
} );
Bedrock and .env. The scalar options bridge straight through, which is the usual reason to reach for this: expanded output on staging, compressed in production, one line per environment.
.env
TSCSSCOMPILER_OUTPUT_STYLE=expanded
config/application.php
if ( $style = env( 'TSCSSCOMPILER_OUTPUT_STYLE' ) ) {
define( 'TSCSSCOMPILER_OUTPUT_STYLE', $style );
}
Pairs are a nested array and do not fit in .env. If you want those from code too, put the define( 'TSCSSCOMPILER_PAIRS', [ … ] ) in config/application.php directly.
Because a TSCSSCOMPILER_* constant supplies it, from wp-config.php, your theme, or a Bedrock bridge. Only that setting locks; everything you did not define a constant for stays editable and saves normally. The notice at the top of the page lists the constants it found, so you know what to go looking for.
Saving is refused on the server for a locked setting, not merely disabled in the browser, and the value stored in the database is left as it was. Define TSCSSCOMPILER_PAIRS and the file-pair list locks the same way.
Yes, per site. Settings and the tracked partials are per-site options and the page is a per-site Tools submenu, so a network-activated plugin has to be configured on each site separately. There is no network admin screen. Deleting the plugin cleans up on every site of the network.
Every .css file it finds in the active theme, annotated with what is known about it: whether it belongs to a compiler pair, with that pair's version and context; whether it happens to be registered in the current request; or whether it is simply a stylesheet that lives in the theme. The theme's own style.css shows the version from its theme header; the other files have none to show. It answers "what else is loading CSS in this theme".
It scans the active theme only, so with a child theme active the parent's stylesheets do not appear. It skips vendor, node_modules, bower_components and hidden folders, and stops at 250 files with a notice.
The settings page is built and measured against WCAG 2.1 AA.
Every field has a label tied to it, including the rows you add with the Add file pair button. Each row is a named group ("File pair 2"), so a screen reader tells you which pair a field belongs to instead of reading four identically-named fields over and over, and the numbering is rewritten when you add or remove a row. Removing a pair moves focus to a neighbouring control rather than dropping it on the page body.
Compile feedback goes through role="status" and role="alert" regions and is announced once, politely. The countdown itself is deliberately not a live region, so it does not read out every second, and it has a Pause auto-recompile button so you can stop it. If a file pair is rejected on save, the page says so in text and explains why, rather than reporting a plain "Settings saved."
Contrast is measured, not estimated: text is at 4.5:1 or better and every control boundary and focus indicator at 3:1 or better. Focus is drawn with a real outline instead of a shadow, so it survives Windows High Contrast mode, where the custom checkbox and selects hand rendering back to the browser. Decorative icons are aria-hidden, headings run without skipping a level, and the status table scrolls inside its own box so the page never scrolls sideways.
Not yet done, for honesty: input that was rejected on save is not handed back to you for correction, so WCAG 2.2's Redundant Entry is still open. If you hit an accessibility problem, please report it.
Yes, German ships with the plugin and the catalogue is complete: every admin string, hint and error message. It loads on every supported WordPress version. Worth knowing if you maintain something similar: WordPress only started registering a plugin's own languages/ directory by itself in 6.8. On 6.3 through 6.7 the plugin registers that directory with WordPress as it loads, and the catalogue is then read on demand at the first translated string. A wordpress.org language pack still takes precedence.
TSCSSCOMPILER_* constant now works on its own and locks the setting it supplies. Until now all of them except TSCSSCOMPILER_PAIRS were ignored unless that one was defined as well. TSCSSCOMPILER_PAIRS also switches version bumping off and locks it, because the versions live in the constant.@import that resolves outside the active theme is now blocked – the CSS target must be a .css file, and server paths are stripped from error messages.@imported partial did not always trigger auto-recompile. When the SCSS compiler reported an included file via a non-canonical path (containing .., . or doubled slashes), the recorded dependency was silently discarded and changes to that partial went undetected. Included paths are now collapsed before being stored.@import-aware dependency tracking – every imported partial is recorded; editing a partial alone triggers auto-recompile.PHP_INT_MAX priority).TSCSSCOMPILER_* constants.tscsscompiler_capability filter for granting access to custom roles.WP_Filesystem API.