Skip to content

Commit

Permalink
add plugin-sass with new onChange/markChanged interface
Browse files Browse the repository at this point in the history
  • Loading branch information
FredKSchott committed Oct 6, 2020
1 parent 62908d9 commit 2227e5d
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 12 deletions.
23 changes: 23 additions & 0 deletions plugins/plugin-sass/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# @snowpack/plugin-sass

This plugin adds [Sass](https://sass-lang.com/) support to any Snowpack project. With this plugin, you can import any `*.scss` Sass from JavaScript and have it compile to CSS.

This plugin also supports `.module.scss` Sass modules. [Learn more.](https://www.snowpack.dev/#import-css-modules)

## Usage

```bash
npm i @snowpack/plugin-sass
```

Then add the plugin to your Snowpack config:

```js
// snowpack.config.js

module.exports = {
plugins: [
'@snowpack/plugin-sass'
],
};
```
19 changes: 19 additions & 0 deletions plugins/plugin-sass/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "1.0.0",
"name": "@snowpack/plugin-sass",
"main": "plugin.js",
"license": "MIT",
"homepage": "https://github.com/pikapkg/snowpack/tree/master/plugins/plugin-sass#readme",
"repository": {
"type": "git",
"url": "https://github.com/pikapkg/snowpack.git",
"directory": "plugins/plugin-sass"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"execa": "^4.0.3",
"npm-run-path": "^4.0.1"
}
}
79 changes: 79 additions & 0 deletions plugins/plugin-sass/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const fs = require('fs');
const path = require('path');
const execa = require('execa');
const npmRunPath = require('npm-run-path');

const IMPORT_REGEX = /\@(use|import)\s*['"](.*?)['"]/g;

function scanSassImports(fileContents, filePath) {
// TODO: Replace with matchAll once Node v10 is out of TLS.
// const allMatches = [...result.matchAll(new RegExp(HTML_JS_REGEX))];
const allMatches = [];
let match;
const regex = new RegExp(IMPORT_REGEX);
while ((match = regex.exec(fileContents))) {
allMatches.push(match);
}

return allMatches
.map((match) => match[2])
.filter((s) => s.trim())
.map((s) => {
if (!path.extname(s)) {
s += '.scss';
}
return path.resolve(path.dirname(filePath), s);
});
}

module.exports = function postcssPlugin(_, options) {
const importedByMap = new Map();

function addImportsToMap(filePath, sassImports) {
for (const imported of sassImports) {
const importedBy = importedByMap.get(imported);
if (importedBy) {
importedBy.add(filePath);
} else {
importedByMap.set(imported, new Set([filePath]));
}
}
}

return {
name: '@snowpack/plugin-sass',
resolve: {
input: ['.scss'],
output: ['.css'],
},
/** when a file changes, also mark it's importers as changed. */
onChange({filePath}) {
const importedBy = importedByMap.get(filePath);
if (!importedBy) {
return;
}
importedByMap.delete(filePath);
for (const importerFilePath of importedBy) {
this.markChange(importerFilePath);
}
},
/** Load the Sass file and compile it to CSS. */
async load({filePath, isDev}) {
const contents = fs.readFileSync(filePath, 'utf8');
// During development, we need to track changes to Sass dependencies.
if (isDev) {
const sassImports = scanSassImports(contents, filePath);
addImportsToMap(filePath, sassImports);
}
// Build the file.
const {stdout, stderr} = await execa('sass', ['--stdin', '--load-path', path.dirname(filePath)], {
input: contents,
env: npmRunPath.env(),
extendEnv: true,
});
// Handle the output.
if (stderr) throw new Error(stderr);
if (stdout) return stdout;
},
};
};
17 changes: 13 additions & 4 deletions snowpack/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,16 @@ class FileBuilder {

export async function command(commandOptions: CommandOptions) {
const {config} = commandOptions;
const isWatch = !!config.buildOptions.watch;
const isDev = !!config.buildOptions.watch;

// Fill in any command-specific plugin methods.
// NOTE: markChanged only needed during dev, but may not be true for all.
if (isDev) {
for (const p of config.plugins) {
p.markChanged = (fileLoc) => onWatchEvent(fileLoc) || undefined;
}
}


const buildDirectoryLoc = config.devOptions.out;
const internalFilesBuildLoc = path.join(buildDirectoryLoc, config.buildOptions.metaDir);
Expand All @@ -293,7 +302,7 @@ export async function command(commandOptions: CommandOptions) {
if (runPlugin.run) {
const runJob = runPlugin
.run({
isDev: isWatch,
isDev: isDev,
isHmrEnabled: getIsHmrEnabled(config),
// @ts-ignore: internal API only
log: (msg, data: {msg: string} = {}) => {
Expand All @@ -304,12 +313,12 @@ export async function command(commandOptions: CommandOptions) {
})
.catch((err) => {
logger.error(err.toString(), {name: runPlugin.name});
if (!isWatch) {
if (!isDev) {
process.exit(1);
}
});
// Wait for the job to complete before continuing (unless in watch mode)
if (!isWatch) {
if (!isDev) {
await runJob;
}
}
Expand Down
18 changes: 13 additions & 5 deletions snowpack/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,24 @@ const sendError = (req: http.IncomingMessage, res: http.ServerResponse, status:
};

export async function startServer(commandOptions: CommandOptions) {
// Start the startup timer!
let serverStart = performance.now();

const {cwd, config} = commandOptions;
const {port: defaultPort, hostname, open} = config.devOptions;
const isHmr = typeof config.devOptions.hmr !== 'undefined' ? config.devOptions.hmr : true;

// Start the startup timer!
let serverStart = performance.now();
const messageBus = new EventEmitter();
const port = await getPort(defaultPort);
// Reset the clock if we had to wait for the user to select a new port.

// Reset the clock if we had to wait for the user prompt to select a new port.
if (port !== defaultPort) {
serverStart = performance.now();
}

const messageBus = new EventEmitter();
// Fill in any command-specific plugin methods.
for (const p of config.plugins) {
p.markChanged = (fileLoc) => onWatchEvent(fileLoc) || undefined;
}

// note: this would cause an infinite loop if not for the logger.on(…) in
// `paint.ts`.
Expand Down Expand Up @@ -1067,6 +1072,9 @@ export async function startServer(commandOptions: CommandOptions) {
inMemoryBuildCache.delete(fileLoc);
filesBeingDeleted.add(fileLoc);
await cacache.rm.entry(BUILD_CACHE, fileLoc);
for (const plugin of config.plugins) {
plugin.onChange && plugin.onChange({filePath: fileLoc});
}
filesBeingDeleted.delete(fileLoc);
}
const watcher = chokidar.watch(Object.keys(config.mount), {
Expand Down
16 changes: 13 additions & 3 deletions snowpack/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ function loadPlugins(
}
plugin.name = plugin.name || name;

// Legacy support: Map the new load() interface to the old build() interface
// Legacy: Map the new load() interface to the old build() interface
const {build, bundle} = plugin;
if (build) {
plugin.load = async (options: PluginLoadOptions) => {
Expand All @@ -289,8 +289,7 @@ function loadPlugins(
return result.result;
};
}
// Legacy support: Map the new optimize() interface to the old bundle()
// interface
// Legacy: Map the new optimize() interface to the old bundle() interface
if (bundle) {
plugin.optimize = async (options: PluginOptimizeOptions) => {
return bundle({
Expand All @@ -310,6 +309,8 @@ function loadPlugins(
});
};
}

// Legacy: handle "defaultBuildScript" syntax
if (
!plugin.resolve &&
plugin.defaultBuildScript &&
Expand All @@ -322,6 +323,15 @@ function loadPlugins(
plugin.resolve = {input, output};
}

// Add any internal plugin methods. Placeholders are okay when individual
// commands implement these differently.
plugin.markChanged = (file) => {
logger.debug(`clearCache(${file}) called, but function not yet hooked up.`, {
name: plugin.name,
});
};

// Finish up.
validatePlugin(plugin);
return plugin;
}
Expand Down
4 changes: 4 additions & 0 deletions snowpack/src/types/snowpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export interface SnowpackPlugin {
knownEntrypoints?: string[];
/** read and modify the Snowpack config object */
config?(snowpackConfig: SnowpackConfig): void;
/** Called when a watched file changes during development. */
onChange({filePath}: {filePath: string}): void;
/** (internal interface, not set by the user) Mark a file as changed. */
markChanged(file: string): void;
}

export interface LegacySnowpackPlugin {
Expand Down

0 comments on commit 2227e5d

Please sign in to comment.