PROJECT AND DEPENDENCIES
I have
and I am using a create-react-app-typescript app with following relevant packages installed:
"dependencies": {
"@types/reactstrap": "^6.0.2",
"bootstrap": "^4.1.3",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-scripts-ts": "2.17.0",
"reactstrap": "^6.3.1",
...
},
...
"devDependencies": {
...
"@types/node": "^10.5.6",
"@types/react": "^16.4.7",
"@types/react-dom": "^16.0.6",
"concurrently": "^4.0.1",
"node-sass-chokidar": "^1.3.3",
"node-sass-glob-importer": "^5.2.0",
"typescript": "^3.0.1"
...
}.
My relevant scripts in package.json are:
"start": "concurrently \"npm:watch-css\" \"react-scripts-ts start\"",
"build-css": "node-sass-chokidar --importer node_modules/node-sass-glob-importer/dist/cli.js src/ -o src/",
"watch-css": "npm run build-css && node-sass-chokidar --watch --recursive --importer node_modules/node-sass-glob-importer/dist/cli.js src/ -o src/"
SASS ARCHITECTURE
I have implemented a 7-1 pattern architecture for my sass files (inside each folder I only have partial files):
![image](https://user-images.githubusercontent.com/10683958/45583395-d224ce00-b897-11e8-9e66-d4705773d940.png)
My main.scss file looks like this:
@charset 'UTF-8';
@import 'abstracts/*';
@import 'vendors/*';
@import 'base/*';
@import 'layout/*';
@import 'components/*';
@import 'pages/*';
@import 'themes/*';
@import '_shame';
THE PROBLEM
node-sass and node-sass-chokidar uses sass-graph to build a Graph object. There is the option --import in node-sass, which allows us to define a custom function as an extension of the importer feature of LibSass.
There are different libraries to use as the importer out there, but I am using particularly node-sass-glob-importer, which allows to write glob patterns in sass' import statements.
This library works perfectly when compiling sass files without watch mode. This is because the imports in any sass file, are parsed by LibSass and then each import statement goes through this library to check if there are some glob patterns in them, if so, the library sends all corresponding paths out of the glob patterns to LibSass again. So far, so good.
In watch mode, node-sass builds a Graph object coming from sass-graph and here is the real problem. A Graph object has an "index" property, which is an object whose keys are the paths of all sass files (partials or not) found under the specified watched directory. The value for each key is an entry with following properties:
- imports: all import paths found in this current sass file
- importedBy: all paths to the files that import this current sass file
- modified: a timestamp with the time this current sass file was modified
For instance, in my sass architecture, the entry object for my _shame.scss file looks like:
{
...
"index": {
"~/_shame.scss":{
"imports": [],
"importedBy": ["~/main.scss"],
"modified": [some_timestamp]
}
}
...
}
since main.scss is the only file that imports this file. This is how node-sass realize when to re-render main.scss, either if it was modified directly, or if any file containing its path in the "importedBy" property was modified. And this is working, of course, I modify _shame.scss and I get in the console that "main.scss" was modified and it is re-compiled again.
Now, when I modify any file inside abstracts folder, for instance "abstracts/_variables.scss" partial file, I do not get main.scss re-compiled. So, I checked the entry for this file and it looks like:
...
"index": {
"~/abstracts/_variables.scss":{
"imports": [],
"importedBy": [],
"modified": [some_timestamp]
}
}
...
Why is this happening? I started debugging and I got to this point when sass-graph is adding main.scss file into the Graph:
![image](https://user-images.githubusercontent.com/10683958/45602075-d8c55980-b9ed-11e8-8dc1-58cc93313184.png)
When parsing import statements at line 69, glob patterns are not resolved by the parse-imports script. As you can see in the red circle, all imports are still there with the glob pattern.
Where does it break? In line 75, more specifically in line 20 inside resolveSassPath method. If we execute the condition the if is evaluating with
scssPath = "[path_start]/abstracts/*"
it will throw an exception, it will be catched but nothing will be done (exception swallowing). Thus, false will be returned as result and in line 76 a continue statement will skip that import (it will do the same for all imports with glob patterns) and only the _shame.scss file (since it has no glob pattern and can be resolved by resolveSassPath method) will be included in the "imports" property of the main.scss file inside the Graph object. So, the final result will be main.scss not taking into consideration that is importing files specified by the glob patterns and each partial file matching the glob patterns not taking into consideration that is imported by main.scss.
SOLUTION
In my opinion we should come to a common way to parse import statements in node-sass. If watch mode is not specified, it is a LibSass and the (if specified) custom importer function responsibility. If watch mode is specified, then sass-graph parses the imports (not supporting glob patterns). So, whatever I can do with the custom importer is not supported in sass-graph.
A temporary solution would be to modify the parse-imports script to support glob patterns when building the graph, or even better, to use the custom importer function passed to node-sass (we should pass it all along to sass-graph).
I volunteer to contribute to fix this, if the rest of the community see this as a real bug.
Opinions, comments and tips are very well welcomed!
Best,
Max.