how to use TS+css+scss in react SSR framework next? - javascript

I'm am using TS + CSS + SCSS in nextJS. I will import some CSS files, but I want to set cssModule:false to those CSS files, then I will import my own SCSS files and set cssModule:true.
The below is my code in next.config.js, it transfers CSS files to module.
const withSass = require("#zeit/next-sass");
const withTypescript = require("#zeit/next-typescript");
const withCSS = require("#zeit/next-css");
module.exports = withTypescript(
withCSS(
withSass({
cssModules: true
})
)
);
Could you please advise me on the right approach of importing CSS files?

The configuration of next-css is global, you either using cssModules or not.
My solution for this was to configure webpack manually to not apply cssModules on files with .global.css suffix.
config.module.rules.forEach(rule => {
if (rule.test.toString().includes('.scss')) {
rule.rules = rule.use.map(useRule => {
if (typeof useRule === 'string') {
return {
loader: useRule,
};
}
if (useRule.loader.startsWith('css-loader')) {
return {
oneOf: [
{
test: /\.global\.scss$/,
loader: useRule.loader,
options: {
...useRule.options,
modules: false,
},
},
{
loader: useRule.loader,
options: useRule.options,
},
],
};
}
return useRule;
});
delete rule.use;
}
});
There is an open PR for next-css to include similar solution to the lib.

Related

How to configure a vue application (with vue-cli) to add nonce attributes to generated script tags?

I have a vue application that is compiled using vue-cli. My vue.config.js file looks like:
'use strict';
module.exports = {
publicPath: `${process.env.CDN_URL || ''}/dist/`,
lintOnSave: true,
transpileDependencies: [],
outputDir: '.tmp/dist',
pages: {
navigator: {
entry: 'vue/home/main.ts',
template: 'views/home/index.ejs',
// Will output to dist/views/home/index.ejs
filename: 'views/home/index.ejs',
},
},
chainWebpack(config) {
// Override the default loader for html-webpack-plugin so that it does not fallback to ejs-loader.
// ejs-loader will use ejs syntax against the template file to inject dynamic values before html-webpack-plugin runs
config.module
.rule('ejs')
.test(/\.ejs$/)
.use('html')
.loader('html-loader');
},
};
I would like to have webpack add nonce="<%= nonce %>" for each script tag that it generates. I see that webpack has a __webpack_nonce__ variable, but I've tried setting that in many parts of the vue.config.js file. I've tried adding it to chainWebpack() and configWebpack(). I've tried adding it to the vue/home/main.ts file. Nothing seems to work. How do I get nonce attributes added to the script tags?
This is vue 2.6.x and vue cli 4.5.x
I ended up writing my own webpack plugin since my use case was a bit more complex than what script-ext-html-webpack-plugin could support. I needed ${nonce} for header tags and <%= nonce %> for body tags. My plugin code is a simple and based off script-ext-html-webpack-plugin mentioned by #Sphinx
const HtmlWebpackPlugin = require('html-webpack-plugin');
class AddNonceToScriptTagsWebpackPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(this.constructor.name, (compilation) => {
const alterAssetTagGroups = compilation.hooks.htmlWebpackPluginAlterAssetTags || HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups;
alterAssetTagGroups.tap(this.constructor.name, (data) => {
data.head = this._addNonceAttribute(data.head || []);
data.body = this._addNonceAttribute(data.body || []);
return data;
});
});
}
_addNonceAttribute(tags) {
return tags.map((tag) => {
if (tag.tagName === 'script') {
tag.attributes = tag.attributes || {};
tag.attributes.nonce = '<%= nonce %>';
} else if (tag.tagName === 'link' && tag.attributes && tag.attributes.as === 'script') {
tag.attributes = tag.attributes || {};
// eslint-disable-next-line no-template-curly-in-string
tag.attributes.nonce = '${nonce}';
}
return tag;
});
}
}
The updated vue.config.js file then looks like:
'use strict';
module.exports = {
publicPath: `${process.env.CDN_URL || ''}/dist/`,
lintOnSave: true,
transpileDependencies: [],
outputDir: '.tmp/dist',
pages: {
navigator: {
entry: 'vue/home/main.ts',
template: 'views/home/index.ejs',
// Will output to dist/views/home/index.ejs
filename: 'views/home/index.ejs',
},
},
configureWebpack: {
plugins: [
new AddNonceToScriptTagsWebpackPlugin(),
],
},
chainWebpack(config) {
// Override the default loader for html-webpack-plugin so that it does not fallback to ejs-loader.
// ejs-loader will use ejs syntax against the template file to inject dynamic values before html-webpack-plugin runs
config.module
.rule('ejs')
.test(/\.ejs$/)
.use('html')
.loader('html-loader');
},
};
One solution should be adds script-ext-html-webpack-plugin into the plugin list of webpack.prod.conf file (or your own webpack configuration file).
new ScriptExtHtmlWebpackPlugin({
custom: {
test: /\.js$/, // adjust this regex based on your demand
attribute: 'nonce',
value: '<%= nonce %>'
}
}),

NextJS with CSS/ SCSS loaders

I'm completely new to NextJS, trying out its SSR features.
I want to setup loaders where I can load 4 types of files into the app:
*.module.css;
*.module.scss;
*.css;
*.scss;
1 and 2 are loaded with CSS modules and 3 and 4 just as regular CSS/SCSS.
How can I do this with #zeit/next-css and #zeit/next-sass?
Currently next-css / next-sass are not supporting this feature, but you can add it by your self to the webpack config.
I've similar but the opposite option, I'm enabling module: true for all imports and for all files that I want to be treated as regular css in (ie. global), I need to add, .global suffix.
This is my modification to the webpack config:
// next.config.js
config.module.rules.forEach(rule => {
if (rule.test && rule.test.toString().includes('.scss')) {
rule.rules = rule.use.map(useRule => {
if (typeof useRule === 'string') {
return {
loader: useRule,
};
}
if (useRule.loader.startsWith('css-loader')) {
return {
oneOf: [
{
test: /\.global\.scss$/,
loader: useRule.loader,
options: {
...useRule.options,
modules: false,
},
},
{
loader: useRule.loader,
options: useRule.options,
},
],
};
}
return useRule;
});
delete rule.use;
}
});
This piece of code looks for the css-loader config and modifies it to support .global.scss suffix.
BTW, you can get updated by following this PR
EDIT
Next version ^9.2.0 has now a native support for css imports with some restrictions.
just wondering whether you've managed to get this working?
I managed to come close with something similar to Felix's answer to get .module.scss and .scss files working with .css only working in #imports within a .scss/.module.scss file. Really would like to be able to get .css files working whilst imported from a component.
My next.config.js file for reference below
const withSass = require('#zeit/next-sass');
const withPlugins = require('next-compose-plugins');
const withSourceMaps = require('#zeit/next-source-maps');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const sassConfig = {
cssModules: true,
cssLoaderOptions: {
importLoaders: 2,
localIdentName: '[local]___[hash:base64:5]',
},
webpack: config => {
config.module.rules.forEach(rule => {
if (rule.test && rule.test.toString().match(/\.(sa|sc)ss$/)) {
rule.rules = rule.use.map(useRule => {
if (typeof useRule === 'string') {
return { loader: useRule };
}
if (useRule.loader.startsWith('css-loader')) {
return {
oneOf: [
{
test: new RegExp(/\module.(sa|sc)ss$/),
exclude: new RegExp(/\.css$/),
loader: useRule.loader,
options: useRule.options,
},
{
loader: useRule.loader,
options: {},
},
],
};
}
return useRule;
});
delete rule.use;
}
});
return config;
},
};
module.exports = withPlugins(
[[withSourceMaps], [withSass, sassConfig]]
);

React - Next.js configuration with cssModules and url-loader

I need to configure next.config.js file in order to use in the same time two different loaders.
This is the first:
const withSass = require('#zeit/next-sass');
module.exports = withSass({
cssModules: true,
cssLoaderOptions: {
importLoaders: 1,
localIdentName: "[local]___[hash:base64:5]",
}
})
And this is the second:
const withSass = require('#zeit/next-sass');
const withCSS = require('#zeit/next-css');
module.exports = withCSS(withSass({
webpack (config, options) {
config.module.rules.push({
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 100000
}
}
})
return config
}
}))
Individually they work great!
But I'm not able to combine them in a single rule so that both can work together.
Thanks for your help!!
You should use next-compose-plugins. Here is a link below for additional reference.
How can I combine multiple loaders (CSS, LESS, ttf, etc) in NextJS config file?
But the code below should solve your issue:
Install
yarn add --dev #zeit/next-css
yarn add --dev #zeit/next-sass file-loader url-loader
Update your next.config.js file
const withPlugins = require('next-compose-plugins');
const sass = require("#zeit/next-sass")
const css = require("#zeit/next-css")
const nextConfig = {
webpack: function (config) {
config.module.rules.push({
test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 100000,
name: '[name].[ext]'
}
}
})
return config
}
}
module.exports = withPlugins([
[css],
[sass, {
cssModules: true
}]
], nextConfig);
Note: You should now be able to import scss modules like this:
import styles from 'your-file-name.module.scss'
Note: Watch out for vendors libs that are not formatted properly in that case you should import as follows:
import "slick-carousel/slick/slick-theme.css";
import "slick-carousel/slick/slick.css";

How can I replace files at compile time using webpack?

I have a project with miltiple configuration. The first config is config.dev.js file that contains some development configiration. I using it in development mode. The second config is config.js file. I using it in production mode.
In the code I using imports:
import * as config from './config.js';
I want to use the first config file in development and the second to production whithout rewriting all of the imports. How can I replace this config depending on the build mode?
This is an old question but I've recently stumbled across the same issue and webpack.NormalModuleReplacementPlugin doesn't seem to work anymore (or at least not in my case, where I used JSON files as config). Instead I found another solution using alias:
const path = require("path");
modules.export = {
...
resolve: {
...
alias: {
[path.resolve(__dirname, "src/conf/config.json")]:
path.resolve(__dirname, "src/conf/config.prod.json")
}
}
...
}
I realize this is an old post, but this is one of the first results on Google, so I thought a better answer would be good.
Webpack has a built in "Normal Module Replacement Plugin".
plugins: [
new webpack.NormalModuleReplacementPlugin(
/some\/path\/config\.development\.js/,
'./config.production.js'
)
]
For my use, I put the env file in a variable Here is an example:
let envFilePath = './environment.dev.js';
if (env === 'production') {
envFilePath = './environment.prod.js';
} else if (env === 'staging') {
envFilePath = './environment.stg.js';
}
module.exports = {
// other webpack stuff
....
plugins:[
new webpack.NormalModuleReplacementPlugin(
/src\/environment\/environment\.js/,
envFilePath
),
...
]
}
You can use webpack file-replace-loader
https://www.npmjs.com/package/file-replace-loader
Example:
//webpack.config.js
const resolve = require('path').resolve;
module.exports = {
//...
module: {
rules: [{
test: /\.config\.js$/,
loader: 'file-replace-loader',
options: {
condition: process.env.NODE_ENV === 'development',
replacement: resolve('./config.dev.js'),
async: true,
}
}]
}
}
I wanted to imitate the Angular fileReplacements syntax so I used a config.json like Angular's and if the configuration key matches the env I pass to webpack, loop through the replacements and create several Webpack module rules.
It's not the most elegant thing ever but this is what I ended up with:
// config.json
{
"title": "Some Title",
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"lan": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.lan.ts"
}
]
}
}
}
// webpack.config.js
const appConfig = require('./config.json');
module.exports = (env, argv) => {
// env.build is like `ng build --prod` as `webpack --env.build=production`
const configuration = appConfig.configurations[env.build];
const fileReplacements = [];
// Safety Check
if(configuration && configuration.fileReplacements) {
// Iterate through replacements
for(const replacement of configuration.fileReplacements) {
// create Webpack module rule
const replace = {
test: path.resolve(replacement.replace),
loader: 'file-replace-loader',
options: {
replacement: path.resolve(replacement.with),
async: true
}
}
fileReplacements.push(replace);
}
}
return {
mode: //...
entry: //...
module: {
rules: [
{
//...
},
// Unpack anywhere here
...fileReplacements
]
}
}
}
This way you don't have to keep messing with webpack and regex tests, just add to the array in config.json
You can also use babel-loader like this:
//webpack.config.js
const resolve = require('path').resolve;
module.exports = {
//...
module: {
strictExportPresence: true,
rules: [{
test: /\.config\.js$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
plugins: [
[
"module-resolver",
{
resolvePath(sourcePath, currentFile, opts) {
if(process.env.NODE_ENV === 'development') {
return sourcePath.substr(0, sourcePath.lastIndexOf('/')) + '/config.dev.js';
} else {
return sourcePath;
}
}
}
]
]
}
}]
}
}
This way you can even define complex algorithms to determine which file you wanna use.
Another way is to use Webpack.DefinePlugin. Especially useful if you have a tiny code block that you want to be included conditionally.
Example follows:
// webpack.conf.js
// Code block that should be inserted or left blank
const ServiceWorkerReg = `window.addEventListener('load', () => {
navigator.serviceWorker.register('service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});`
appConfig = {
plugins: [
new webpack.DefinePlugin({
__SERVICE_WORKER_REG: isDev ? '' : ServiceWorkerReg,
}),
]
...
}
// Some module index.js
__SERVICE_WORKER_REG
... // other non conditional code

'Cannot find module' when using webpack loaders

I use custom webpack for my app with Next.js:
const path = require('path');
const Webpack = require('webpack');
module.exports = {
webpack: (config, { dev }) => {
config.plugins.push(
new Webpack.DefinePlugin([{
__BROWSER__: "typeof window !== 'undefined'",
__NETLIFY_BRANCH__: JSON.stringify(process.env.BRANCH),
__CONFIG__: JSON.stringify({
GRAPHQL_URL: process.env.GRAPHQL_URL,
SEGMENT_WRITEKEY: process.env.SEGMENT_WRITEKEY,
}),
}])
)
config.module.rules.push(
{
test: /\.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'graphics/[name].[ext]',
},
},
},
{
test: /\.svg$/,
loader: [
{
loader: 'babel-loader',
query: {
presets: ['es2015']
}
},
{
loader: 'svg-react-loader',
},
{
loader: 'string-replace-loader',
options: {
search: ' xmlns="http://www.w3.org/2000/svg"',
replace: '',
},
},
],
},
);
return config;
},
};
But when I try to add image to my component, my localhost launched, but I receive the error:
Cannot find module '../assets/logo.svg'
I import my image to the component as usual, and without an image, my app is running. Maybe I should add something to my webpack?
All images set in dir assets.
import logo from '../assets/logo.svg';
export default class Layout extends React.Component {
render () {
return (
<img src={logo} alt="logo" />
)
}
}
The issue (from what I understand from this: https://github.com/zeit/next.js/issues/544) is that Next.js doesn't use Webpack for server side rendering, only for client side, so you can't import an SVG like you do when it's purely a client side React project (i.e. by adding a rule for .svg files and run those files through a loader chain).
A possible solution is mentioned here, where the SVG import is not done by a Webpack loader, but by Babel (which is used on the server side code in Next.js).
It seems to work for me, like this:
npm i babel-plugin-inline-react-svg --save
add/merge the following to your Babel config (more info on how to set up a custom Babel configuration here):
"plugins": [
"inline-react-svg"
]
in your component/page, use this (the name of the variable, LogoSVG, is important and shouldn't be changed):
import LogoSVG from '../assets/logo.svg';
and use it as a React component:
return <LogoSVG alt="logo" />

Categories