Giter VIP home page Giter VIP logo

sass-extract's Introduction

sass-extract

CI status npm version

Extract structured variables from your sass files with no effort. Have all your style variables defined in style files, while being able to use them in javascript for things that cannot be styled with css such as complex visualisations or other dynamic content.

If you are using webpack make sure to also check out the sass-extract-loader.

demo.gif Demo of sass-extract using the sass-extract-loader


Installation

npm install --save node-sass sass-extract

Note that the node-sass compiler have to be installed as it is a peer dependency of sass-extract.

API

The API is deliberately kept very similar to that of node-sass. This is because sass-extract can be used as a replacement that will add variable extraction as an additional feature to compiling the sass into css.

render(compileOptions, extractOptions)

An augmented version of the node-sass render function that in addition to rendering css also extract sass variables into rendered.vars.

See node-sass for documentation of the compileOptions object.

To be able to extract variables across multiple files using the @import directive you need to provide either file or includePaths for import lookups.

const sassExtract = require('sass-extract');

sassExtract.render({
  file: 'path/to/my/styles.scss'
})
.then(rendered => {
  console.log(rendered.vars);
  console.log(rendered.css.toString());
});
renderSync(compileOptions, extractOptions)

A synchronous version of the render function.

const sassExtract = require('sass-extract');

const rendered = sassExtract.renderSync({
  file: 'path/to/my/styles.scss'
});

console.log(rendered.vars);
console.log(rendered.css.toString());
extract(rendered, { compileOptions, extractOptions })

Extract variables for a rendered sass files.

See node-sass for documentation of the compileOptions object.

Generally you will pass the same compileOptions to both node-sass for rendering and sass-extract for extraction.

To be able to extract variables across multiple files using the @import directive you need to provide either file or includePaths for import lookups.

const sass = require('node-sass');
const sassExtract = require('sass-extract');

const rendered = sass.renderSync({
  file: 'path/to/my/styles.scss'
});

sassExtract.extract(rendered, {
  file: 'path/to/my/styles.scss'
})
.then(vars => {
  console.log(vars);
});
extractSync(rendered, { compileOptions, extractOptions })

A synchronous version of the extract function.

const sass = require('node-sass');
const sassExtract = require('sass-extract');

const rendered = sass.renderSync({
  file: 'path/to/my/styles.scss'
});

const vars = sassExtract.extractSync(rendered, {
  file: 'path/to/my/styles.scss'
});

console.log(vars);

Extract options

Can be provided in all render or extract methods.

  • extractOptions.plugins: []: Provide any plugins to be used during the extraction. Provide a string '<plugin>' for the node module or bundled plugin name, or alternatively an object { plugin: '<plugin>', options: {} } to provide any options to the plugin. Note: A plugin instance can be passed directly instead of a plugin name which allows importing the plugin separately

Variable context

Sass features both global and local variables.

// style.scss

$globalVariable1: 123px; // global

div {
  $localVariable1: red; // Local
  $globalVariable2: 1em !global; // global
}

The extracted variables returned from sass-extract are namespaced by the context where they are declared, so global variables will be placed in vars.global;

Global variables

A global variable is accessible from anywhere within that file, as well as from files that @import the file where the variable is declared. A variable is considered global when it is declared outside of any selector, or if the annotation !global is included after the variable expression.

// styleA.scss
$a: 123px;
@import './styleB.scss';
// styleB.scss
$b: 456px;
// extracted variables of styleA.scss
{
  global: {
    $a: {/*...*/},
    $b: {/*...*/}
  }
}
Local variables

A local variable is only accessible from within the selector where it is declared and from children to that selector.

Currently sass-extract supports extraction of global variables only. Local variables based on selectors and APIs to utilize them is on the roadmap. Global variables is however covers the most likely use cases of this tool at this point, and thus made sense to be the fundamental first feature.

Overrides

Variables in sass can be overridden any number of times and across multiple files when using @import. A variable can only have one final value within its context, but intermediate values can be assigned to separate variables in order to be retained.

sass-extract will always extract the final computed value of a variable, no matter the number of overrides. This means however that variables can have multiple different expressions and be specified in multiple files, while still always having one value.

// styleA.scss
$a: 123px;
$b: $a;
@import './styleB.scss';
// styleB.scss
$a: 456px;
// extracted variables of styleA.scss
{
  global: {
    $a: { value: 456 },
    $b: { value: 123 }
  }
}

Data types

Sass has a few different data types that a variable can have. These are detected by sass-extract automatically and the structure of the result of a variable value will be adapted to the type of the variables. Below follows descriptions for how each type of variable is extracted.

General variable value structure

Each variable extracted is available under its context by a key which is the variable name as specified in the sass file.

// style.scss
$myVariable: 123px;
// Corresponding variable result object
{
  global: {
    $myVariable: {
      type: 'SassNumber',
      sources: [ 'path/to/style.scss' ],
      value: 123, // Extracted value
      unit: 'px', // Data type specific metadata
      declarations: [
        expression: '123px',
        flags: { default: false, global: false },
        in: 'path/to/style.scss',
        position: { cursor: 0, line: 1, column: 0 }
      ]
    }
  }
}

Each of the variable results will have the same general structure and potentially additional type specific fields.

Field Description
type A string describing the data type of the extracted variables
sources An array of all file paths where this variables is declared
declarations An array of declarations of the variable
declarations[i].expression The raw expression of the variable definition
declarations[i].flags Describes any present flags such as !default or !global
declarations[i].in The file where the declaration was found
declarations[i].position The exact position of the declaration in the file

Note that sources and expressions are both arrays, see Overrides for details about this.

SassString
$variable: 'string';
{
  type: 'SassString',
  value: 'string'
}
SassBoolean
$variable: true;
{
  type: 'SassBoolean',
  value: true
}
SassNull
$variable: null;
{
  type: 'SassNull',
  value: null
}
SassNumber

SassNumbers contains both the extracted number and their unit

$variable: 123px;
{
  type: 'SassNumber',
  value: 123,
  unit: 'px'
}
SassColor

SassColors contains extracted colors in both rgba and hex formats

$variable: #FF0000;
{
  type: 'SassColor',
  value: {
    r: 255,
    g: 0,
    b: 0,
    a: 1,
    hex: '#FF0000'
  }
}

Or alternatively

$variable: rgba(0, 255, 0, 0.5);
{
  type: 'SassColor',
  value: {
    r: 0,
    g: 255,
    b: 0,
    a: 0.5,
    hex: '#00FF00'
  }
}
SassList

SassLists contains recursive types as an array

$variable: 1px solid black;
{
  type: 'SassList',
  separator: ' ',
  value: [
    {
      type: 'SassNumber',
      value: 1,
      unit: 'px'
    },
    {
      type: 'SassString',
      value: 'solid'
    },
    {
      type: 'SassColor',
      value: {
        r: 0,
        g: 0,
        b: 0,
        a: 1,
        hex: '#000000'
      }
    }
  ]
}

Or when it's comma separated

$variable: tahoma, arial;
{
  type: 'SassList',
  separator: ',',
  value: [
    {
      type: 'SassString',
      value: 'tahoma'
    },
    {
      type: 'SassString',
      value: 'arial'
    }
  ]
}
SassMap

SassMaps contains recursive types as an object with matching field names

$variable: ( width: 10em, height: 5em );
{
  type: 'SassMap',
  value: {
    width: {
      type: 'SassNumber',
      value: 10,
      unit: 'em'
    },
    height: {
      type: 'SassNumber',
      value: 5,
      unit: 'em'
    }
  }
}

Plugins

sass-extract has plugin support which can be used to modify the behavior and output of the extraction process.

A plugin is a simple object containing a run(options) function. This will be called at each extraction run. The run function should return a plugin interface with a function for each stage of the process where the plugin want to be injected. Each hook can return a modified version of its input which will modify the final output. Plugins are run in order as a pipeline, and one plugin can modify the input of a plugin later in the chain. Plugins can store state between stages within the run function as it is called once for each extraction run. Plugins provided to the plugin through render or extract APIs will be passed as the only argument to the run function.

For examples see the bundled plugins in src/plugins.

Pluggable stages
  • postValue({ value, sassValue }) => { value, sassValue }: Executed for each extracted variable value with the computed value as well as the raw value provided by sass. The returned value object can be modified in order to change the output of that variable
  • postExtract(extractedVariables) => extractedVariables: Executed at the end of an extraction with the complete result. The return extractedVariables object can be modified in order to change the final result of the extraction
Bundled plugins

There are some bundled plugins that comes with the library. To use them simply require('sass-extract/lib/plugins/<plugin>') and add them to the plugin array of the extraction options, or specify them by module name such as { plugins: ['minimal'] }.

Plugin Description
serialize Get a serialized variant of each variable instead of a deconstructed object. E.g. 123px is extracted as { value: 123px } instead of the default { value: 123, unit: 'px' }
compact Remove all metadata about variables and only output the actual value for each variable
minimal Combines serialize and compact to create a small serialized representation of the extracted variables
filter Filter the results based on a combination of property names and/or variable types
Bundled plugin options
filter
Option field Description
except.props = ['$my-prop'] Will remove any properties with a name in the provided array
except.types = ['SassNumber'] Will remove any properties with a type in the provided array
only.props = ['$my-prop'] Will remove any properties with a name not in the provided array
only.types = ['SassNumber'] Will remove any properties with a type not in the provided array

Note: Empty arrays are ignored and treated as no filtering should be applied for that selection

What is sass-extract?

sass-extract is a tool that compiles sass files into a JSON object containing its variables and their computed values. This can be very useful when you need to style something that cannot be styled by css, or when you need to know details about the styles in your javascript.

It is built on top of node-sass which is using the performant libsass compiler. sass-extract is using native features in libsass in order to extract computed variables from your stylesheet that will be identical to the values in the generated css. This means you can expect to get the correct extracted value of a variable like $myVariable: $multiplier * 200px / 10, even if the $multipler variable is a variable defined in a separate imported sass file.

The purpose of this library is to give you the option to keep all of your style information in style files as expected. Maybe you are rendering on a canvas and cannot use css for styling the content, then this library will help you out.

There are other solutions to the same problem such as placing some of your style variables in a JSON file an including them into the sass, but with sass-extract you can skip that additional complexity of separate files and preprocessing and just get the variables directly from the sass itself.

Requirements

  • node-sass >= 3.8.0
  • node >= 4

Contributing

sass-extract is using babel in order to take advantage of recent langugae features.

Compile source
npm run compile
Running tests
npm test
Commits

In order to have readable commit messages and the ability to generate a changelog the commit messages should follow a certain structure.

To make it easier install npm install -g commitizen and commit using git-cz.

Generate changelog using npm install -g conventional-changelog and npm run changelog.

Releasing new versions
  1. Make changes
  2. Commit those changes
  3. Set new version in package.json
  4. npm run changelog
  5. Commit package.json and CHANGELOG.md files
  6. Tag
  7. Push

sass-extract's People

Contributors

bencergazda avatar bentsai avatar evanlovely avatar jgerigmeyer avatar jgranstrom avatar jrencz avatar phlmn avatar roblan avatar wendt88 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

sass-extract's Issues

Problems with Multiline Comment Syntax

Hey, first of all thanks for this very useful library!

I'm experiencing a strange behavior when using the /* */ comment Syntax.

I've got a file with color definitions and when I use the multiline syntax variables are missing in the extracted data. When I use the single line syntax everything seems fine.

After some testing I found out that all variables between the first and the last appearance of /* */ are ignored.

The Code:

const extracted = sassExtract.renderSync({
    file: 'test_scss/_colors.scss'
});

console.log(extracted.vars);

The Working Sass:

// Default gray
$_colorGray: #666;
// Default light gray
$_colorLightGray: #eee;
// Default gray for buttons
$_colorButtonGray: #656666;
// Default red
$_colorRed: #E74327;
// Default color for strokes
$colorStroke: #DEDADA;

Output

{
    global: {
        '$_colorGray': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        },
        '$_colorLightGray': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        },
        '$_colorButtonGray': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        },
        '$_colorRed': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        },
        '$colorStroke': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        }
    }
}

The Broken Sass

// Default gray
$_colorGray: #666;
/* Default light gray */
$_colorLightGray: #eee;
// Default gray for buttons
$_colorButtonGray: #656666;
/* Default red */
$_colorRed: #E74327;
// Default color for strokes
$colorStroke: #DEDADA;

Output

{
    global: {
        '$_colorGray': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        },
        '$_colorRed': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        },
        '$colorStroke': {
            type: 'SassColor',
            value: [Object],
            sources: [Object],
            expressions: [Object]
        }
    }
}

Problem parsing IE hacks

Found another issue, and I realise this is not really on you, but rather in scss-parser but it doesn't seem to be open source.

If you have a style such as

&:focus {
   outline: 0;
   outline: thin dotted \9; /* IE6-9 */
}

scss-parser will throw an error trying to parse the \:

Unhandled rejection Error: Can't handle character: "\" (39:25)
    at InputStream.err (C:\myproj\node_modules\scss-parser\dist\input-stream.js:122:13)
    at Object.err (C:\myproj\node_modules\scss-parser\dist\input-stream.js:161:20)
    at TokenStream.err (C:\myproj\node_modules\scss-parser\dist\token-stream.js:297:40)
    at TokenStream.read_next (C:\myproj\node_modules\scss-parser\dist\token-stream.js:352:12)
    at TokenStream.peek (C:\myproj\node_modules\scss-parser\dist\token-stream.js:254:27)
    at Object.peek (C:\myproj\node_modules\scss-parser\dist\token-stream.js:625:21)
    at C:\myproj\node_modules\scss-parser\dist\parse.js:90:30
    at Array.reduce (<anonymous>)
    at Parser.is_type (C:\myproj\node_modules\scss-parser\dist\parse.js:89:21)
    at Parser.is_punctuation (C:\myproj\node_modules\scss-parser\dist\parse.js:128:27)
    at Parser.is_interpolation (C:\myproj\node_modules\scss-parser\dist\parse.js:176:19)
    at C:\myproj\node_modules\scss-parser\dist\parse.js:426:20
    at Parser.maybe_function (C:\myproj\node_modules\scss-parser\dist\parse.js:609:14)
    at Parser.parse_atom (C:\myproj\node_modules\scss-parser\dist\parse.js:420:19)
    at Parser.parse_node (C:\myproj\node_modules\scss-parser\dist\parse.js:353:25)
    at Parser.parse_block (C:\myproj\node_modules\scss-parser\dist\parse.js:539:25)

For what it's worth I hate these dodgy style hacks but I didn't write it 😄

Replacing this token with an empty string before calling (0, _scssParser.parse) in parse.js makes the parser happy again

DeprecationWarning: Module Chunks

I'm getting the following warning/error when parsing a bootstrap _variable.scss file. I'm currently working with next.js

 ERROR  Failed to compile with 5 errors

This dependency was not found:

* fs in ./node_modules/mkdirp/index.js, ./node_modules/node-sass/lib/extensions.js and 3 others

To install it, you can run: npm install --save fs
(node:9903) DeprecationWarning: Module.chunks: Use Module.forEachChunk/mapChunks/getNumberOfChunks/isInChunk/addChunk/removeChunk instead

Here's my peace of code:

import { ThemeProvider } from 'styled-components';
import { renderSync } from 'sass-extract';

const theme = renderSync(
    {
        file: 'scss/themes/bootstrap/_variables.scss'
    },
    {
        plugins: ['sass-extract-js']
    }).vars;

Packages version:

"sass-extract": "^2.1.0",
"sass-extract-js": "^0.3.0",
"node-sass": "^4.7.2",

Even though the variables are parsed and I can access them in a plan JSON object, but it's a bit weird 😟

Update node-sass peer dependency version?

Do you guys have any intention to upgrade node-sass peer dependency version?
For those who are using node-sass v5.0.0 or v6.0.x, they have problems to use your package

$ npm i
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/node-sass
npm ERR!   dev node-sass@"5.0.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer node-sass@"^3.8.0 || 4" from [email protected]
npm ERR! node_modules/sass-extract
npm ERR!   dev sass-extract@"2.1.0" from the root project

Include doc comments

I find this plugin great for documentation purposes (used in Storybook pages).

Would it be possible to somehow add doc comments found in the preceding lines?

Eg:
Screenshot 2020-11-15 at 16 02 32

So object for this variable would also include {... comments: ['Color for emphasized texts']}
This would enable us to keep documentation together with code.

Render fails for SCSS file encoded in UTF8-BOM

Hello,

I am trying to make this lib works in my webpack config file, without any success.

If I do

const sass = require('node-sass');
const renderedSass = sass.renderSync({ file: "file.scss" });
console.log(renderedSass);

It works, and I get:

{ stats:
   { entry: ...,
     start: 1543849333359,
     includedFiles: [... ],
     end: 1543849333363,
     duration: 4 },
  css:
   <Buffer 40 ... 738 more bytes> }

However, if I do:

const sassExtract = require('sass-extract');
const renderedSass = sassExtract.renderSync({ file: "file.scss" });
console.log(renderedSass);

It does not work:

× 「config」: An error occurred while trying to load .\webpack.config.prod.js
              Did you forget to specify a --require?
.\node_modules\@webpack-contrib\config-loader\lib\LoadConfigError.js:7
    const stack = error.stack.split('\n').slice(1);
                              ^

TypeError: Cannot read property 'split' of undefined
    at new LoadConfigError (.\node_modules\@webpack-contrib\config-loader\lib\LoadConfigError.js:7:31)
    at module.exports (.\node_modules\@webpack-contrib\config-loader\lib\load.js:85:11)
    at module.exports (.\node_modules\@webpack-contrib\config-loader\lib\index.js:14:15)
    at load (.\node_modules\webpack-command\lib\config.js:55:12)
    at module.exports (.\node_modules\webpack-command\lib\index.js:45:10)
    at run (.\node_modules\webpack-command\lib\cli.js:116:5)
    at Object.<anonymous> (.\node_modules\webpack-command\lib\cli.js:19:3)
    at Module._compile (internal/modules/cjs/loader.js:707:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:718:10)
    at Module.load (internal/modules/cjs/loader.js:605:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:544:12)
    at Function.Module._load (internal/modules/cjs/loader.js:536:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:760:12)
    at startup (internal/bootstrap/node.js:303:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:872:3)

What I tried

  1. Removing node_modules + node-cache and doing a fresh npm install
  2. Rebuilding node-sass: npm rebuild node-sass --force
  3. Using render instead of renderSync
  4. Requiring node-sass before sass-extract

Environment

  • Win10 x64
  • npm version: 6.4.1
  • node version : v11.0.0
  • node-sass package version: 4.10.0
  • sass-extract package version: 2.1.0

Thanks for your help.

Add a built in plugin for flexible transformation of output format

There should be a built in plugin that is configurable in order to produce different formats and structure of extracted output. This can for example be to extract numbers as strings that include the number unit, instead of parsing it to a javascript number type.

If you have any specific cases where you are currently required to modify the output format please include that use case here so that we can bake it in as a feature for a built in transformation plugin.

Support sass map keys of arbitrary data type

Sass supports map keys to be of any data types, while sass-extract currently expects them to be strings. This means that map keys such as white will fail due to being treated as a color by libsass.

The symptoms can currently be treated by wrapping map keys in single quotes to make sure they are treated as strings.

Option to get variables as JS object instead of JSON

First, huge props on this library, it's just what I was looking for. 👍

I was wondering if it would be possible to add an option to get the sass variables as a JS object instead of the data structure as you have it. For example:

Instead of this:

{
  "$primary": {
    "type": "SassColor",
    "value": {
      "r": 255,
      "g": 239,
      "b": 213,
      "a": 1,
      "hex": "#ffefd5"
    },
    "sources": [
      "/Users/adamgruber/Sites/sassy-styled/src/colors.scss"
    ],
    "expressions": [
      "papayawhip"
    ]
  },
  "$baseMargin": {
    "type": "SassNumber",
    "value": 4,
    "unit": "px",
    "sources": [
      "/Users/adamgruber/Sites/sassy-styled/src/colors.scss"
    ],
    "expressions": [
      "4px"
    ]
  },
  "$baseBorder": {
    "type": "SassList",
    "value": [
      {
        "type": "SassNumber",
        "value": 1,
        "unit": "px"
      },
      {
        "type": "SassString",
        "value": "solid"
      },
      {
        "type": "SassColor",
        "value": {
          "r": 0,
          "g": 0,
          "b": 0,
          "a": 1,
          "hex": "#000000"
        }
      }
    ],
    "sources": [
      "/Users/adamgruber/Sites/sassy-styled/src/colors.scss"
    ],
    "expressions": [
      "1px solid black"
    ]
  }
}

I'd like to get this:

{
  $primary: 'rgba(255, 239, 213)',
  $baseMargin: '4px',
  $baseBorder: '1px solid rgba(0, 0, 0)'
}

The reason is that I'm creating an app using styled-components which supports themes. The themes are just plain JS objects. I have an existing style guide written in SASS and I wanted to use the variables in the style guide as the theme for my components.

I am currently able to do what I need using this library and a function I wrote to transform the sass-extract data structure to plain JS. I just thought it might be something that could be added to your library. I was thinking it could be added to the rendered.vars.global object as a toJS() function.

Then it could be used like this:

const style = require('sass-extract-loader!./theme.scss');
const theme = style.global.toJS();
/* theme ==>
{
  $primary: 'rgba(255, 239, 213)',
  $baseMargin: '4px',
  $baseBorder: '1px solid rgba(0, 0, 0)'
}
*/

I created this gist that shows the function I'm using to do the transform.

Unable to parse variables which contain a forward-slash `/`

Failing example:

$my-font-definition: normal 16px/1.2 sans-serif;

Error:

Unhandled rejection Error: error in C function ___SV_INJECT_L1VzZXJzL1JoeXMvUHJvamVjdHMvRW1waXJlZC9ESVAvRGlnaXRhbCBJUC9zcmMvdWkvc3JjL2ZyYW1ld29yay9zdHlsZXMvY29uZmlnL19jb2xvdXItY29uZmlnLnNjc3M_IG_my-font-defintion_7:: Unsupported sass variable type 'SassError'
    at options.error ({root}/node_modules/node-sass/lib/index.js:291:26)

When calling the `findImportedPath` the `url` should be normalized to prevent issues on Windows

Context

When using sass-extract on NodeJS, at least together with node-sass-tilde-importer, the path of the import appears with a backslash \, while the includedFilesMap presents paths with forwardslash /.

Expected result

It should handle the import's path seamlessly, as if it was done on Linux or MacOS.

Actual result

It throws the error: Can not determine imported file for url 'C:\my\path\to\the\module\bla.scss' imported in C:/my/path/to/the/file/that/imports/it.scss

Note the difference in back and forward slashes.

Proposed solution

The solution should be passing the url parameter as normalizePath(url).

Importer won't recognise partials (underscore-prefixed files)

In lib/importer.js, in findImportedPath the mechanism tries to match unknown url against all given include paths.

the mechanism doesn't seem to know what sass knows: that foo/bar/_foo.scss is the same as foo/bar/foo.scss

IMO an attempt to recognise url as partial given without leading underscore should be made.

Enable hex values to be capitalized

I think it should be possible to customize how hex values look, especially if you want to maintain a kind of uniformity across your project.
I propose adding a capitalizeHexValue (if you think of a better name, lemme know) option to the serialize plugin.

I am willing to take this up.

Support custom node-sass `importer` compileOption.

Right now, any custom importer fn is ignored (replaced by the sass-extract specific importer), and not passed through to node-sass. I see this issue has been mentioned in both sass-extract loader (jgranstrom/sass-extract-loader#1) and babel-plugin-sass-extract (jgranstrom/babel-plugin-sass-extract#1), but I don't see a fix for it yet.

For example, I have sass files that use webpack's ~ shortcut to import node modules:

@import '~my-module/sass/utility';

When not using webpack to compile, I pass a simple importer option through to node-sass:

importer: url => {
  if (url[0] === '~') {
    url = path.resolve('node_modules', url.substr(1));
  }
  return { file: url };
},

This doesn't work with sass-extract, however (see https://github.com/jgranstrom/sass-extract/blob/master/src/extract.js#L39).

I'll submit a PR shortly that fixes this.

Error when extracting variables with values containing calc function

Context
When using sass-extract on NodeJS, at least together with package sass, it fails on renderSync function call if variables string contains values with calc function.

Example:

const sass = require('sass');
const sassExtract = require('sass-extract');
sassExtract.renderSync({ data: '$var1: calc(100% - 70px - .625rem);' }, { implementation: sass });

Dependencies:

"sass-extract": "^3.0.0",
"sass": "^1.70.0"

Expected result
It should handle calc function correctly and extract it as regular string value

Actual result

Error: Unsupported sass constructor 'SassCalculation0'
  ╷
4 │     $___SV_INJECT_ZGF0YQ_IG_var1_0: ___SV_INJECT_ZGF0YQ_IG_var1_0($var1); 
  │                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ╵
  stdin 4:37  root stylesheet
    at getConstructorName (/home/d/Projects/test2/node_modules/sass-extract/lib/util.js:120:13)
    at createStructuredValue (/home/d/Projects/test2/node_modules/sass-extract/lib/struct.js:68:40)
    at Object.injectedFunction (/home/d/Projects/test2/node_modules/sass-extract/lib/inject.js:24:51)
    at LegacyJavaScriptObject.apply$2 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:30528:23)
    at Object.apply$2$x (/home/d/Projects/test2/node_modules/sass/sass.dart.js:1008:43)
    at _parseFunctions___closure1.call$0 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:102040:16)
    at Object.wrapJSExceptions (/home/d/Projects/test2/node_modules/sass/sass.dart.js:8407:25)
    at _parseFunctions__closure0.call$1 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:102032:30)
    at _EvaluateVisitor__runBuiltInCallable_closure6.call$0 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:97901:35)
    at _EvaluateVisitor1._evaluate0$_addExceptionSpan$1$3$addStackFrame (/home/d/Projects/test2/node_modules/sass/sass.dart.js:96354:23)
    at _EvaluateVisitor1._evaluate0$_addExceptionSpan$2 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:96368:19)
    at _EvaluateVisitor1._evaluate0$_runBuiltInCallable$3 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:95809:24)
    at _EvaluateVisitor1._evaluate0$_runFunctionCallable$3 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:95732:55)
    at _EvaluateVisitor_visitFunctionExpression_closure7.call$0 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:97755:25)
    at _EvaluateVisitor1._evaluate0$_addErrorSpan$1$2 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:96398:23)
    at _EvaluateVisitor1._evaluate0$_addErrorSpan$2 (/home/d/Projects/test2/node_modules/sass/sass.dart.js:96416:19) {

Parse error in Bootstrap v4.1

Hello

I'm getting this error

Unhandled rejection Parsing error: Please check validity of the block starting from line #4

2 |   // Custom variable values only support SassScript inside `#{}`.
3 |   @each $color, $value in $colors {
4*|     --#{$color}: #{$value};
5 |   }


Syntax: scss
Gonzales PE version: 4.2.3

while i'm running this code

const sass = require('node-sass')
const sassExtract = require('sass-extract')
const path = require('path')

const rendered = sass.renderSync({
  file: path.join(process.cwd(), 'node_modules/bootstrap/scss/bootstrap.scss')
});

sassExtract.extract(rendered, {
  file: path.join(process.cwd(), 'node_modules/bootstrap/scss/bootstrap.scss')
})
.then(vars => {
  console.log(vars);
});

Module build failed: Error: ENOENT: no such file or directory, open 'D:\localhost\test\D:\localhost\test\styles.scss'

I get the following error on Windows, which is just started to happen after upgrading to 0.5.0.

Hash: 98d8a632299bd19989c3
Version: webpack 3.3.0
Time: 222ms
	   Asset     Size  Chunks             Chunk Names
dist/test.js  3.69 kB       0  [emitted]  test.js
   [0] ./test.js 569 bytes {0} [built]
   [1] ./node_modules/sass-extract-loader!./styles.scss 166 bytes {0} [built] [failed] [1 error]

ERROR in ./node_modules/sass-extract-loader!./styles.scss
Module build failed: Error: ENOENT: no such file or directory, open 'D:\localhost\test\D:\localhost\test\styles.scss'
  at Error (native)

 @ ./test.js 15:0-65

It looks like it appends one path to another: D:\localhost\test\D:\localhost\test\styles.scss. It is a custom test environment, but the examples in sass-extract-loader also fail now on Windows:

ERROR in ./~/sass-extract-loader!./src/gradient-rectangle/gradient-rectangle.scss
Module build failed: Error: ENOENT: no such file or directory, open 'D:\Downloads\sass-extract-loader-master2\examples\visual\D:\Downloads\sass-extract-loader-master2\examples\visual\src\gradient-rectangle\gradient-rectangle.scss'
	at Error (native)
 @ ./src/gradient-rectangle/gradient-rectangle.js 4:12-68
 @ ./src/index.js
 @ multi (webpack)-dev-server/client?http://localhost:8080 webpack/hot/dev-server ./index.js

If I revert the changes from f2bc8b1#diff-19b7dd157933fc27ee38258cb120e62f, it works.

Problem parsing Windows line endings

See attached test case (sorry about the ZIP, Github doesn't support .scss directly):
test.zip

On Windows, files with multiple lines can't be parsed by scss-parser.

" (1:16)d rejection Error: Can't handle character: "
    at InputStream.err (C:\myproject\node_modules\scss-parser\dist\input-stream.js:122:13)
    at Object.err (C:\myproject\node_modules\scss-parser\dist\input-stream.js:161:20)
    at TokenStream.err (C:\myproject\node_modules\scss-parser\dist\token-stream.js:297:40)
    at TokenStream.read_next (C:\myproject\node_modules\scss-parser\dist\token-stream.js:352:12)
    at TokenStream.peek (C:\myproject\node_modules\scss-parser\dist\token-stream.js:248:26)
    at TokenStream.eof (C:\myproject\node_modules\scss-parser\dist\token-stream.js:282:26)
    at Object.eof (C:\myproject\node_modules\scss-parser\dist\token-stream.js:631:16)
    at Parser.parse_stylesheet (C:\myproject\node_modules\scss-parser\dist\parse.js:266:27)
    at module.exports (C:\myproject\node_modules\scss-parser\dist\parse.js:748:17)
    at parse (C:\myproject\node_modules\scss-parser\dist\index.js:27:10)
    at parseDeclarations (C:\myproject\node_modules\sass-extract\lib\parse.js:131:35)
    at processFile (C:\myproject\node_modules\sass-extract\lib\process.js:25:51)
    at C:\myproject\node_modules\sass-extract\lib\process.js:58:29
    at Array.map (<anonymous>)
    at processFiles (C:\myproject\node_modules\sass-extract\lib\process.js:57:22)
    at C:\myproject\node_modules\sass-extract\lib\extract.js:126:49

If I convert the line endings to lf with eol it works correctly.

I tried passing the linefeed option in to render options, but it didn't seem to have any effect.

Thanks!

Just wanted to thank you for putting this together.

Sass plugin usage

Hello. Am failing to understand how to apply the plugins. Could you please provide like a code sample.

For example to output simpler css data values instead of the complex sass data structures.

Thanks

Raw css imports are not supported

Hello,

we're using raw css imports (sass/libsass#754), but sass-extract fails on them, because the custom imported always adds a .scss before trying to import the file.

Error: Can not determine imported file for url 'sanitize.css/sanitize.scss' imported in src/stylesheets/main.scss
    at Object.module.exports.renderSync (node_modules/node-sass/lib/index.js:439:16)
    at extractSync (node_modules/sass-extract/lib/extract.js:171:22)
    at Object.renderSync (node_modules/sass-extract/lib/render.js:46:44)
    at Object.<anonymous> (sass-test.js:7:34)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.runMain (module.js:605:10)
    at run (bootstrap_node.js:427:7)
    at startup (bootstrap_node.js:151:9)
    at bootstrap_node.js:542:3

Thank you!

Silently breaks on @mixin and @function blocks which has 3 or more parameters with default value

Try to @import a file, which contains a @function or @mixin which has more than three parameters with a default value:

@function test($attr1, $attr2, $attr3: null, $attr4: null, $attr5: null) {
	@return 'hello';
}

The extractor will halt and you will have to terminate the batch job manually.

Strange, but it doesn't halt on eg. @function test($attr1, $attr2, $attr3, $attr4, $attr5), so it's not the @function˛ or @mixin block itself, which causes the error. It's also OK with @function test($attr1, $attr2, $attr3, $attr4: null, $attr5: null) {, so when there's only two (or less) attributes with default a value.

Parsing error while parsing Bulma SASS

The extractor runs into a parsing error when working with the Bulma framework .

The issue seems to stem from the differences between old-style SASS and new-style SCSS. Specifically, I write my styles using the new style, but I also @import some old-style files from a package.

How to replicate the issue I'm seeing:

Set up a new project:

package.json

{
  "name": "sass-extract-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "bulma": "^0.6.2",
    "node-sass": "^4.7.2",
    "sass-extract": "^2.1.0"
  }
}

main.scss - notice that I'm using a new-style .scss file.

// import the initial variables from bulma
//
@import "sass/utilities/initial-variables";
@import "sass/utilities/functions.sass";

// import bulma framework (which happens to be a set of old-style .sass files)
//
@import 'bulma';

test.js

const sass = require('node-sass');
const sassExtract = require('sass-extract');

const includePaths = [
    `${__dirname}/node_modules/bulma`,
];

// load my new-style "base" file
//
const file = `${__dirname}/main.scss`;

const rendered = sass.renderSync({ file, includePaths });

//console.log(rendered);
console.log(rendered.css.toString());

sassExtract.extract(rendered, { file })
    .then((vars) => {
        console.log(vars);
    });

The renderSync step with node-sass works fine - the resulting CSS looks the way it should. Passing it to the sass-extract extract results in the following error:

Unhandled rejection Parsing error: Please check validity of the block starting from line #3

1 | // Float
3*| .is-clearfix
4 |   +clearfix

Syntax: scss
Gonzales PE version: 4.2.3

gonzales-pe is clearly attempting to use the wrong syntax (scss instead of sass) when parsing this particular file. And digging around in the source to this package, I notice that the scss syntax is hard-coded in parse.js at line 147. I've tried manually changing this (just to see what happens), but it breaks with other errors - clearly this is going to take more than just a simple fix.

Start babel plugin

Although @jgranstrom other project, sass-extract-loader does great job as intended it may not always fit all needs.

My case:
I'm managing a bunch of internal company projects that use webpack but under that there's a layer of webpack-less configuration logic which have no access to webpack loaders. All those projects use tleunen/babel-plugin-module-resolver to resolve module aliases. I decided to configure it like that to allow fast parsing without a necessity for starting the entire webpack machinery which - for this use case - is too heavy and totally unnecessary.

Sadly: importing scss files that way is not possible because it's (at least for now) a webpack thing.
I had to move importing scss files using sass-extract-loader to a file that is not accessed using the pipeline I mentioned (which, in turn, is against designed architecture).

For a use case like mine it would be great to have a babel plugin which would do just the same as webpack loader does, yet outside of webpack.

I'm thinking about starting a babel-plugin-sass-extract (or something named in a similar way), or I'm willing to participate in a project of such scope

Compact should not drop unit / new plugin needed

I recently added the compact plugin to my build process incorporating sass-extract via sass-extract-loader and I was quite happy about it until I found out that if original value is e.g. 8px then what I get with compact enabled is just 8 (number).

Main cause I started using compact is the fact it drops the source paths, which I was particularly interested in.

It's impossible to distinguish 8px from 8em with compact - it removes too much.

I suggest either compact should preserve unit for what it now considers a primitive or there should be a new plugin bundled, which works like compact form colors and maps but instead of numbers, it returns strings (with unit) for primitives

Unhandled rejection TypeError: Cannot read property 'injectedData' of undefined

Maybe I'm missing something but isn't sass-extract suppose to work like drop in replacement for node-sass?

The following works as expected for me:

const sass = require('node-sass');
const sassExtract = require('sass-extract');

function extract() {

    const rendered = sass.render({
        file: 'scss/style.scss'
    });
}

But if I change from sass.render to sassExtract.render like this:

const sass = require('node-sass');
const sassExtract = require('sass-extract');

function extract() {

    const rendered = sassExtract.render({
        file: 'scss/style.scss'
    });
}

I get the following error:

Unhandled rejection TypeError: Cannot read property 'injectedData' of undefined
    at makeExtractionCompileOptions (C:\dev\style\packages\colors\node_modules\sass-extract\lib\extract.js:52:61)
    at C:\dev\style\packages\colors\node_modules\sass-extract\lib\extract.js:101:36
    at tryCatcher (C:\dev\style\packages\colors\node_modules\bluebird\js\release\util.js:16:23)
    at Promise._settlePromiseFromHandler (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:512:31)
    at Promise._settlePromise (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:569:18)
    at Promise._settlePromise0 (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:614:10)
    at Promise._settlePromises (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:693:18)
    at Promise._fulfill (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:638:18)
    at Promise._resolveCallback (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:432:57)
    at Promise._settlePromiseFromHandler (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:524:17)
    at Promise._settlePromise (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:569:18)
    at Promise._settlePromise0 (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:614:10)
    at Promise._settlePromises (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:693:18)
    at Promise._fulfill (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise.js:638:18)
    at PromiseArray._resolve (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise_array.js:126:19)
    at PromiseArray._promiseFulfilled (C:\dev\style\packages\colors\node_modules\bluebird\js\release\promise_array.js:144:14)

Am I missing something?

Order of @import-ing files changes randomly

It looks like sometimes the order of @import-s are changing randomly:

D:\localhost\sass-extract-loader-master\examples\basics>npm start

> [email protected] start D:\Downloads\sass-extract-loader-master\examples\basics
> webpack 1>/dev/null && node dist/app.bundle


--- Style Varibles ---

global:
  $value:
	type:        SassString
	value:       OK
	sources:
	  - D:/localhost/sass-extract-loader-master/examples/basics/src/_style1.scss
	  - D:/localhost/sass-extract-loader-master/examples/basics/src/_style2.scss
	expressions:
	  - 'OK'
	  - 'NOT OK'



D:\localhost\sass-extract-loader-master\examples\basics>npm start

> [email protected] start D:\localhost\sass-extract-loader-master\examples\basics
> webpack 1>/dev/null && node dist/app.bundle


--- Style Varibles ---

global:
  $value:
	type:        SassString
	value:       NOT OK
	sources:
	  - D:/localhost/sass-extract-loader-master/examples/basics/src/_style2.scss
	  - D:/localhost/sass-extract-loader-master/examples/basics/src/_style1.scss
	expressions:
	  - 'NOT OK'
	  - 'OK'

The content of style.scss:

@import "style1";
@import "style2";

The content of _style1.scss:

$value: 'NOT OK';

The content of _style2.scss:

$value: 'OK';

It should return 'OK' as the value of $value, but as it can be seen, _style2.scss gets loaded before _style1.scss in the second run.

(I'm not sure, whether this is an issue with sass-extract or sass-extract-loader. I used sass-extract-loader's "Basic" example)

Problem parsing varargs

Hey,
as mentioned in #15 varargs in the SCSS lead to a crash. Since scss-parser isn't that well maintained, maybe gonzales-pe would be a good replacement.

At the moment I'm checking if it is an easy swap and would give it a try, what are your thoughts on that?

Importers - callback vs. Promise

Node-sass uses a callback function to return the resolved path (or contents), so as how sass-extract does in the first (rendering) phase, but on the second (extracting) phase, sass-extract expects a Promise for its internally used importer() (importer.js:102 and 113).

If we would like to use a custom importer, we need to prepare it for both usecases.

If it's OK I can send a PR with a modified, callback-based importer.js

Freezing of the JavaScript process due to regexp matching

I've stumbled upon a curious edge case while using this module. I'm attempting to parse out the variables from an SCSS file which imports an SCSS file that has utility functions defined. When parsing the entry file I found out that my node process started to randomly freeze without no explanation what so ever.

I managed to trace down the source of the issue to the regexp based parsing of the SCSS files. When it's trying to extract implicit variables from the file it ends up with the following snippet:

/(\$[\w-_]+)\s*:\s*((.*?\n?)+?);/g.exec('@function decimal-round ($number, $digits: 0, $mode: round) \n@function decimal-ceil ($number, $digits: 0) \n@function decimal-floor ($number, $digits: 0) \n\n@function strip-unit($number) \n')

If you execute the code above in the node REPL or even in your browser console it will completely freeze up.

// _decimal.scss | MIT License | gist.github.com/terkel/4373420
@function decimal-round ($number, $digits: 0, $mode: round) {
    $n: 1;
    // $number must be a number
    @if type-of($number) != number {
        @warn '#{ $number } is not a number.';
        @return $number;
    }
    // $digits must be a unitless number
    @if type-of($digits) != number {
        @warn '#{ $digits } is not a number.';
        @return $number;
    } @else if not unitless($digits) {
        @warn '#{ $digits } has a unit.';
        @return $number;
    }
    @for $i from 1 through $digits {
        $n: $n * 10;
    }
    @if $mode == round {
        @return round($number * $n) / $n;
    } @else if $mode == ceil {
        @return ceil($number * $n) / $n;
    } @else if $mode == floor {
        @return floor($number * $n) / $n;
    } @else {
        @warn '#{ $mode } is undefined keyword.';
        @return $number;
    }
}
@function decimal-ceil ($number, $digits: 0) {
    @return decimal-round($number, $digits, ceil);
}
@function decimal-floor ($number, $digits: 0) {
    @return decimal-round($number, $digits, floor);
}

//CSS-Tricks
@function strip-unit($number) {
  @if type-of($number) == 'number' and not unitless($number) {
    @return $number / ($number * 0 + 1);
  }
  @return $number;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.