How to use scss with css modules / postcss inside components - javascript

I'm failing to figure out how to setup my webpack in order to be able and use scss modules inside my components like:
import styles from './ComponentStyles.scss'
So far I tried configuring webpack to use sass-loader alongside postcss-loader but had no luck:
{
test: /\.scss$/,
loaders: [
'isomorphic-style-loader',
'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
'postcss-loader',
'scss-loader'
]
}
Note isomorphic-style-loader is a library I use instead of style-loader due to server rendering requirements, in its github page docs they actually use postcss-loader with .scss extension, but in my case scss is not compiled if I follow their example.

I had to do some tinkering myself, but eventually landed on the following
package.json
"autoprefixer": "^6.3.1",
"css-loader": "^0.23.1",
"extract-text-webpack-plugin": "^1.0.1",
"node-sass": "^3.8.0",
"sass-loader": "^3.1.2",
"style-loader": "^0.13.0"
webpack config
...
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const autoprefixer = require('autoprefixer');
...
const config = {
...
postcss: [
autoprefixer({
browsers: ['last 2 versions']
})
],
plugins: [
new ExtractTextPlugin('css/bundle.css'),
],
module: {
loaders: [
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('style', 'css!postcss!sass')
}
]
}
...
};
bootstrap.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import style from './scss/style.scss';
ReactDOM.render(
<App/>,
document.getElementById('my-app')
);
For those interested: what's happening here is bootstrap.jsx is the webpack entry point, and by importing our raw scss file (via relative path), we tell webpack to include it during the build process.
Also, since we've specified a loader for this file extension in our configuration (.scss), webpack is able to parse style.scss and run it through the defined loaders from right to left: sass --> post-css --> css.
We then use extract-text-webpack-plugin to pull the compiled CSS out from bundle.js, where it would normally reside, and place it in a location (css/bundle.css) relative to our output directory.
Also, using extract-text-webpack-plugin is optional here, as it will simply extract your CSS from bundle.js and plop it in a separate file, which is nice if you utilize server-side rendering, but I also found it helpful during debugging, since I had a specific output location for the scss I was interested in compiling.
If you're looking to see this in action, here's a small boilerplate that uses it: https://github.com/mikechabot/react-boilerplate

Related

Vue CLI sourcemaps to style part of vue component file

I'm playing with Vue CLI project. I have configured startup project, set some development changes like those:
package.json
"dependencies": {
"bootstrap": "^4.3.1",
"core-js": "^3.0.1",
"preload-it": "^1.2.2",
"register-service-worker": "^1.6.2",
"vue": "^2.6.10",
"vue-router": "^3.0.3",
"vuetify": "^1.5.14",
"vuex": "^3.1.1"
},
"devDependencies": {
"#vue/cli-plugin-babel": "^3.7.0",
"#vue/cli-plugin-pwa": "^3.7.0",
"#vue/cli-service": "^3.7.0",
"fontello-cli": "^0.4.0",
"node-sass": "^4.9.0",
"sass-loader": "^7.1.0",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"vue-cli-plugin-vuetify": "^0.5.0",
"vue-template-compiler": "^2.5.21",
"vuetify-loader": "^1.2.2"
}
vue.config.js
module.exports = {
configureWebpack: {
devtool: process.env.NODE_ENV === 'development'
? 'inline-source-map'
: false,
},
css: {
sourceMap: process.env.NODE_ENV === 'development'
}
}
babel.config.js
module.exports = {
presets: [
[
'#vue/app',
{ useBuiltIns: 'entry' }
]
]
}
But sourcemaps to vue files are still generated wrongly (to scss files works ok).
After clicking href to vue component
Note:
lot of versions of same file in webpack://./
only part that is in tag is visibile in source editor (maybe this is a cause)
file from mounted filesystem workspace is not used
And this is how original file looks like - it is possible to edit it via Chrome devtools
Is it possible to fix that so also element inspector tab (style) will provide proper source target?
EDIT 1
Simplest setup:
Install Vue CLI (3.7)
Add my vue.config.js (to enable sourcemaps)
Run npm run serve
EDIT 2
Same for Vue CLI 3.5
I also created repo with test project, but like I wrote it is just startup project with my config.
https://github.com/l00k/vue-sample
EDIT 3
Vue-cli github issue
https://github.com/vuejs/vue-cli/issues/4029
So far I did not found solution - at least using Vue CLI.
But I have found workaround.
But first of all - whole problem is not about Vue CLI but it is something with vue-loader-plugin IMO. I think so because while using clean setup with vue and webpack I also see that problem.
I have found out that it is related to wrong sourcemap generated for those parts of Vue file and
Source for those part is strip to only content of those tags. That is probably why browser could not map it to source. Also path to source file in sourcemap is wrong.
I have prepared additional loader for webpack which fixes those sourcemaps.
Check sm-fix-loader in repo below.
I dont know does it fix all issues, but at least in my cases it works awesome.
What works ok:
Build NODE_ENV=development webpack
SCSS inline (in vue file) and in separate file <style src="...">
TS / JS inline (in vue file) and in separate file <script src="...">
HRM NODE_ENV=development webpack-dev-server --hotOnly
SCSS inline (in vue file) and in separate file <style src="...">
It also reloads styles without reloading page itself :D
TS / JS inline (in vue file) and in separate file <script src="...">
Repo with working example:
https://github.com/l00k/starter-vue
Step by step solution:
Enable css sourcemaps in vue.config.js:
module.exports = {
css: {sourceMap: true},
Move all scss from components to separate files, collate them in index.scss and import index.scss via App.vue. This will solve lots of problems with vue-css-sourcemaps (caused by Webpack, Devtools and vue-cli), and somewhat simplify your workflow. If you need scoping, scope manually via #selectors (Importing SCSS file in Vue SFC components without duplication with Webpack)
To go further, you may need to set up CSS extraction for node_modules only, as another mysterious bug ruins styling as soon as you touch any css in devtools:
devtool: 'cheap-source-map',
plugins: process.env.NODE_ENV === 'development' ?
([new MiniCssExtractPlugin()]) : [],
module: {
rules: [
process.env.NODE_ENV === 'development' ?
(
{
// test: /node_modules/,
test: /node_modules\/.+\.scss/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2,
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
plugins: () => [require('autoprefixer')],
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {sourceMap: true}
}
]
}) : {}
],
}
If you extract all css, you'll loose hmr (hot module reloading = reload on edit), but since you don't really edit scss in your node_modules, you extact only them.
All in all, this fixed all vue css-related sourcemap issues with Devtools and enabled hot-editing right in browser.

Loading SASS Modules with TypeScript in Webpack 2

I have a simple project set up using TypeScript, ReactJS, and SASS, and would like to bundle it all using Webpack. There's plenty of documentation on how to achieve this with JavaScript and regular old CSS. However, I can't find any documentation that combines the loaders I need and uses Webpack 2 syntax (rather than the original Webpack syntax for loaders). Thus, I'm unsure of how to create the correct configuration.
You can find my webpack.config.js file here. How would I modify the configuration so that TypeScript accepts my SCSS modules, and so that Webpack properly bundles my SCSS with my TypeScript?
This may also be helpful: when I run Webpack at the moment, I get the following error:
ERROR in ./node_modules/css-loader!./node_modules/typings-for-css-modules-loader/lib?{"namedExport":true,"camelCase":true}!./node_modules/sass-loader/lib/loader.js!./src/raw/components/styles.scss
Module build failed: Unknown word (1:1)
> 1 | exports = module.exports = require("../../../node_modules/css-loader/lib/css-base.js")(undefined);
| ^
2 | // imports
3 |
4 |
# ./src/raw/components/styles.scss 4:14-206
# ./src/raw/components/greetings/greetings.tsx
# ./src/raw/index.tsx
# multi ./src/raw/index.tsx
ERROR in [at-loader] ./src/raw/components/greetings/greetings.tsx:3:25
TS2307: Cannot find module '../styles.scss'.
Note that ./src/raw/index.tsx is the entry point of my application, ./src/raw/components/greetings/greeting.tsx is my only React component, and ./src/raw/components/styles.scss is my only SCSS file.
The typings-for-css-modules-loader is a drop-in replacement for css-loader (technically it uses css-loader under the hood) and that means it takes CSS and transforms it to JavaScript. You're also using the css-loader, and that fails because it receives JavaScript, but expected CSS (as JavaScript is not valid CSS, it fails to parse).
Additionally, you are not using CSS modules, because you're not setting the modules: true option on the CSS loader (or typings-for-css-modules-loader, which passes it on to css-loader).
Your .scss rule should be:
{
test: /\.scss$/,
include: [
path.resolve(__dirname, "src/raw")
],
use: [
{ loader: "style-loader" },
{
loader: "typings-for-css-modules-loader",
options: {
namedexport: true,
camelcase: true,
modules: true
}
},
{ loader: "sass-loader" }
]
}
Here is a little extended version (since the above did somehow not work for me), using another package (css-modules-typescript-loader) derived from the stale typings-for-css-modules-loader.
In case anybody runs into the same problems - this is a configuration that works for me:
TypeScript + WebPack + Sass
webpack.config.js
module.exports = {
//mode: "production",
mode: "development", devtool: "inline-source-map",
entry: [ "./src/app.tsx"/*main*/ ],
output: {
filename: "./bundle.js" // in /dist
},
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js", ".css", ".scss"]
},
module: {
rules: [
{ test: /\.tsx?$/, loader: "ts-loader" },
{ test: /\.scss$/, use: [
{ loader: "style-loader" }, // to inject the result into the DOM as a style block
{ loader: "css-modules-typescript-loader"}, // to generate a .d.ts module next to the .scss file (also requires a declaration.d.ts with "declare modules '*.scss';" in it to tell TypeScript that "import styles from './styles.scss';" means to load the module "./styles.scss.d.td")
{ loader: "css-loader", options: { modules: true } }, // to convert the resulting CSS to Javascript to be bundled (modules:true to rename CSS classes in output to cryptic identifiers, except if wrapped in a :global(...) pseudo class)
{ loader: "sass-loader" }, // to convert SASS to CSS
// NOTE: The first build after adding/removing/renaming CSS classes fails, since the newly generated .d.ts typescript module is picked up only later
] },
]
}
};
Also put a declarations.d.ts in your project:
// We need to tell TypeScript that when we write "import styles from './styles.scss' we mean to load a module (to look for a './styles.scss.d.ts').
declare module '*.scss';
And you will need all these in your package.json's dev-dependencies:
"devDependencies": {
"#types/node-sass": "^4.11.0",
"node-sass": "^4.12.0",
"css-loader": "^1.0.0",
"css-modules-typescript-loader": "^2.0.1",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"ts-loader": "^5.3.3",
"typescript": "^3.4.4",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.0"
}
Then you should get a mystyle.d.ts next to your mystyle.scss containing the CSS classes you defined, which you can import as a Typescript module and use like this:
import * as styles from './mystyles.scss';
const foo = <div className={styles.myClass}>FOO</div>;
The CSS will automatically be loaded (injected as a style element into the DOM) and contain cryptic identifiers instead of your CSS classes in the .scss, to isolate your styles in the page (unless you use :global(.a-global-class) { ... }).
Note that the first compile will fail whenever you add CSS classes or remove them or rename them, since the imported mystyles.d.ts is the old version and not the new version just generated during compilation. Just compile again.
Enjoy.

How to add a separate CSS file for .scss files using webpack?

I have a pretty simple JavaScript app I'm compiling using Webpack. I have the application splitting into two separate bundles now - App and Vendor. App contains my custom code while the vendor files contains the frameworks.
The app bundle contains all the JavaScript in my app directory, but there are also some Sass files in there as well. I'm trying to get those to compile into a separate CSS bundle file.
Through some research, I figured out I needed to use the extract-text-webpack-plugin with a webpack Sass compiler and style loaders.
Here is my webpack.config file:
var webpack = require('webpack')
var ExtractTextPlugin = require("extract-text-webpack-plugin")
module.exports = {
context: __dirname + '/app',
entry: {
app: './app.module.js',
vendor: ['angular','angular-route']
},
output: {
path: __dirname + '/bundle',
filename: 'app.bundle.js'
},
module: {
loaders: [
{ test: /\.scss$/, loader: ExtractTextPlugin.extract("style- loader", "css-loader") }
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'vendor', /* filename= */'vendor.bundle.js'),
new ExtractTextPlugin("styles.css")
]
}
I have the following dependencies installed using npm:
"css-loader": "^0.23.1",
"extract-text-webpack-plugin": "^1.0.1",
"node-sass": "^3.8.0",
"sass-loader": "^4.0.0",
"style-loader": "^0.13.1",
"webpack": "^1.13.1"
The problem is when I bundle using webpack, I get the following error:
Module not found: Error: Cannot resolve 'file' or 'directory' ./../node_modules/css-loader/index.js
And
Module not found: Error: Cannot resolve 'file' or 'directory' ./../node_modules/style-loader/addStyles.js
I'm only including one sass file in my main application file at the moment. In my main app.js file, I have: require('./styles.scss') Any ideas why this is happening?
To those of you that may be having the same issue, and you're running Windows, the following solution worked for me:
At the top of our webpack.config add the following:
var path = require('path');
Then change where you define your context to:
context: path.resolve(__dirname, "folder_name")

Client-side Prism.js with npm

I'm trying to use Prism.js syntax highlighter client-side as an npm dependency, instead of loading it from <script src="..."> tags. Here is the Prism reference in package.json
{
"dependencies": {
"prismjs": "^1.5.1"
}
}
And the way I'm trying to use it in my code
import Prism from 'prismjs'
Prism.highlightAll();
This produces the following results:
Tokenizing works for basic languages (html, javascript...)
Tokenizing does not work for other specific languages (lua, handlebars...)
For all languages, syntax coloring isn't applied (css file doesn't seem loaded)
So I'm wondering
Are there other language-specific packages (like prismjs-handlebars for instance)?
Are there theme-specific packages (like prism-okaidia for instance) which would import the css?
--
TL;DR
How to load/use Prism.js client-side from npm instead of from script tags?
I eventually found the way to do this.
1. Add style-loader and css-loader to package.json
{
"dependencies": {
"style-loader": "^0.13.1",
"css-loader": "^0.23.0",
"prismjs": "^1.5.1"
}
}
2. Load css files in webpack.config.js
module: {
loaders: [
{
test: /\.css/,
loader: 'style-loader!css-loader'
}
]
}
3. Import desired files in the application
import Prism from 'prismjs'
import 'prismjs/themes/prism-okaidia.css'
import 'prismjs/components/prism-handlebars.min.js'
import 'prismjs/components/prism-lua.min.js'
Prism.highlightAll();

Webpack externals should not be imported

My app contains different modules, moduleA, moduleB and moduleC. These will be included as dependencies in package.json of my App.
All of these components contain some common scripts, react, react-dom, lodash, etc.
What is the webpack config to build these modules SEPARATELY and then require them in my app? I'm very confused with webpack doc.
Attempt 1: using externals
I defined a common module with index.js containg:
require('react');
require('react-dom');
require('redux');
require('react-redux');
require('moment');
require('lodash');
Then the config file exposed these:
module: {
loaders: [{
test: require.resolve("react"),
loader: "expose?React"
},
...
This common.js is then included before any other file. Then inside each module, using externals I have:
externals: {
"react": "React",
"react-dom": "ReactDom",
...
},
Problem
If I use import React from 'react' in any of my modules, a new instance of react is imported, causing invariance problem and eventual addComponentAsRefTo error.
However, I can perfectly use React as it is globally available.
Attempt 2: CommonsChunkPlugin
Still include common.js from the top. But this time for each module, use vendors:
entry: {
"app": "./src/myentry.jsx",
"vendor": ["react", "react-dom"]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
]
Problem
Each module creates its own vendor file. How do I then combine all vendor files?
I think there are two approaches:
1) load react/react-dom from a CDN, and your bundle separately:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<script src="app.js"></script>
This way, you should not import react (it's already loaded and exposed globally). ReactDOM.render, for instance, should work just fine. If you import, you are going to load into your bundle another instance of React (from ./node_modules), which will throw an error and increase your bundle size.
2) the second option (which I use) is to configure webpack to create a common/vendor bundle:
entry: {
"app: "./src/myentry.jsx",
"vendor": ["react", "react-dom"]
},
output: {
filename: "[name].js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
]
Webpack will correctly separate the codes and generate both app.js and vendor.js. Then you should just load them on your page:
<script src="vendor.js"></script>
<script src="app.js"></script>
if you want to use a remote or cdn react react-dom etc files, why you require them in your common.js. as your know CMD, if your require in your common.js file
require('react');
require('react-dom');
require('redux');
require('react-redux');
require('moment');
require('lodash');
then the variable React, ReactDom is not a golbal variable. your cann't visit it in your other files.
you can do it like this:
window.React = require('react');
window.ReactDom = require('react-dom');
window.Redux = require('redux');
window.ReactRedux = require('react-redux');
window.monment = require('moment');
window.lodash = require('lodash');
and insert such a srcript tag into your html <srcipt src="/common.bunld.js">
just make sure common.js is in your webpack entry.

Categories