Build webpack entries without dependency - Next.js - javascript

I wanted to add custom entries to Next.js Webpack config to run them directly with chrome.runtime.executeScript for my browser extension. previously in CRA I could just add entries and remove splitChunks and runtimeChunk optimizations and it'd run without a problem but now in Next.js, Webpack builds them as a webpackChunk and they depend on main.js and I can't run them using chrome.runtime.executeScript.
Here's my webpack config in next.config.js:
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["#svgr/webpack"],
});
if (config.target === "web" && config.mode === "production") {
config.optimization.splitChunks = {
cacheGroups: {
default: false,
},
};
config.optimization.runtimeChunk = false;
config.output = {
...config.output,
filename: "static/chunks/[name].js",
};
// Setting entries and return config
return Object.assign({}, config, {
entry: () => {
return config.entry().then((entry) => {
const newEntry = {
...entry,
background: "./src/browser-scripts/background.ts",
getMetas: "./src/browser-scripts/getMetas.ts",
};
return newEntry;
});
},
});
}
return config;
}

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 %>'
}
}),

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

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.

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]]
);

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

webpack watch mode with CopyWebpackPlugin causes infinite loop

In webpack, CopyWebpackPlugin causes infinite loop when webpack is in watch mode. I tried to add watchOptions.ignored option but it doesn't seem to work.
My webpack config is following:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const config = {
entry: {
'res': './src/index.js'
},
output: {
filename: '[name].min.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new CopyWebpackPlugin([
{ from: 'dist', to: path.resolve(__dirname, 'docs/js') }
], {})
],
watchOptions: {
ignored: path.resolve(__dirname, 'docs/js')
}
};
module.exports = config;
Any help would be appreciated.
With CopyWebpackPlugin, I've experienced the infinite loop too. I tried all kinds of CopyWebpackPlugin configurations with no luck yet. After hours of wasted time I found I could hook into the compiler and fire off my own copy method.
Running Watch
I'm using webpack watch to watch for changes. In the package.json, I use this config so I can run npm run webpackdev, and it will watch for file changes.
"webpackdev": "cross-env webpack --env.environment=development --env.basehref=/ --watch"
My Workaround
I've added an inline plugin with a compiler hook, which taps into AfterEmitPlugin. This allows me to copy after my sources have been generated after the compile. This method works great to copy my npm build output to my maven target webapp folder.
// Inline custom plugin - will copy to the target web app folder
// 1. Run npm install fs-extra
// 2. Add fix the path, so that it copies to the server's build webapp folder
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
// Debugging
console.log("########-------------->>>>> Finished Ext JS Compile <<<<<------------#######");
let source = __dirname + '/build/';
// TODO Set the path to your webapp build
let destination = __dirname + '/../dash-metrics-server/target/metrics-dash';
let options = {
overwrite: true
};
fs.copy(source, destination, options, err => {
if (err) return console.error(err) {
console.log('Copy build success!');
}
})
});
}
}
The Workaround Source
Here's my webpack.config.js in total for more context. (In this webpack configuration, I'm using Sencha's Ext JS ExtWebComponents as the basis.)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { BaseHrefWebpackPlugin } = require('base-href-webpack-plugin');
const ExtWebpackPlugin = require('#sencha/ext-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const fs = require('fs-extra');
module.exports = function(env) {
function get(it, val) {if(env == undefined) {return val;} else if(env[it] == undefined) {return val;} else {return env[it];}}
var profile = get('profile', '');
var emit = get('emit', 'yes');
var environment = get('environment', 'development');
var treeshake = get('treeshake', 'no');
var browser = 'no'; // get('browser', 'no');
var watch = get('watch', 'yes');
var verbose = get('verbose', 'no');
var basehref = get('basehref', '/');
var build_v = get('build_v', '7.0.0.0');
const isProd = environment === 'production';
const outputFolder = 'build';
const plugins = [
new HtmlWebpackPlugin({template: 'index.html', hash: false, inject: 'body'}),
new BaseHrefWebpackPlugin({ baseHref: basehref }),
new ExtWebpackPlugin({
framework: 'web-components',
toolkit: 'modern',
theme: 'theme-material',
emit: emit,
script: './extract-code.js',
port: 8080,
packages: [
'renderercell',
'font-ext',
'ux',
'd3',
'pivot-d3',
'font-awesome',
'exporter',
'pivot',
'calendar',
'charts',
'treegrid',
'froala-editor'
],
profile: profile,
environment: environment,
treeshake: treeshake,
browser: browser,
watch: watch,
verbose: verbose,
inject: 'yes',
intellishake: 'no'
}),
new CopyWebpackPlugin([{
from: '../node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js',
to: './webcomponents-bundle.js'
}]),
new CopyWebpackPlugin([{
from: '../node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js.map',
to: './webcomponents-bundle.js.map'
}]),
// Debug purposes only, injected via script: npm run-script buildexample -- --env.build_v=<full version here in format maj.min.patch.build>
new webpack.DefinePlugin({
BUILD_VERSION: JSON.stringify(build_v)
}),
// This causes infinite loop, so I can't use this plugin.
// new CopyWebpackPlugin([{
// from: __dirname + '/build/',
// to: __dirname + '/../dash-metrics-server/target/test1'
// }]),
// Inline custom plugin - will copy to the target web app folder
// 1. Run npm install fs-extra
// 2. Add fix the path, so that it copies to the server's build webapp folder
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
// Debugging
console.log("########-------------->>>>> Finished Ext JS Compile <<<<<------------#######");
let source = __dirname + '/build/';
// TODO Set the path to your webapp build
let destination = __dirname + '/../dash-metrics-server/target/metrics-dash';
let options = {
overwrite: true
};
fs.copy(source, destination, options, err => {
if (err) return console.error(err)
console.log('Copy build success!');
})
});
}
}
];
return {
mode: environment,
devtool: (environment === 'development') ? 'inline-source-map' : false,
context: path.join(__dirname, './src'),
//entry: './index.js',
entry: {
// ewc: './ewc.js',
app: './index.js'
},
output: {
path: path.join(__dirname, outputFolder),
filename: '[name].js'
},
plugins: plugins,
module: {
rules: [
{ test: /\.(js)$/, exclude: /node_modules/,
use: [
'babel-loader',
// 'eslint-loader'
]
},
{ test: /\.(html)$/, use: { loader: 'html-loader' } },
{
test: /\.(css|scss)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' }
]
}
]
},
performance: { hints: false },
stats: 'none',
optimization: { noEmitOnErrors: true },
node: false
};
};
So I know this question is very old at this point, but I was running into this endless loop issue again recently and found a couple of solutions.
Not the cleanest method, but if you add an "assets" folder, which can be completely empty, to the root of your project, it seems to only compile after your sources folder changes.
The better method I have found is within the webpack config. The original poster mentioned about using ignored which does seem to fix the issue if you instruct webpack to watch file changes after the initial build and to ignore your dist/output folder...
module.exports = {
//...
watch: true,
watchOptions: {
ignored: ['**/dist/**', '**/node_modules'],
},
};

Categories