I've been struggling with something (react, but doesn't matter).
Imagine the following setup:
// modules/A/index.ts
export { actions } from '/actions'
export { default as RootComponent } from './component' // imagine this to be big, I tried adding 250kb of data to it.
export { default as reducer } from './reducer'
export { default as saga } from './saga'
Now.. In the app, there is the following:
// index.js
import {actions} from './modules/A';
const LazyComponent = React.lazy(() => import('./modules/A').then(res => {
// doing something with the reducer/saga
return {default: res.RootComponent}
}));
// LazyComponent as a route, using an action from actions to trigger something (if present)
All this works fine, except, the moment when I statically import the actions from the module, no chunk will be generated anymore and everything ends up in the main js. If I would import the actions from the actions file itself from the module, it will generate a chunk for the dynamic import, like desired.
If there anything that can be done to get my desired effect:
statically import something from the module, but load the rest only on demand (because of the size)?
The idea is, that the module might be extracted as a dependency later on (es module), and then the pkg.module will be some index, so how to support code splitting like that?
Simplified Webpack config (copy from a different project, didn't really go through it yet) (v4.43):
const config = {
target: 'web',
entry: {
app: './src/index.tsx',
},
output: {
filename: '[name].[chunkhash].bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: '/',
},
resolve: {
extensions: ['.tsx', '.ts'],
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'thread-loader',
options: {
poolTimeout: isProduction ? 500 : Infinity,
},
},
{
loader: 'ts-loader',
options: {happyPackMode: true},
},
],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(ttf|eot|svg|jpg|gif|png|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader',
},
],
},
plugins: [
...(isProduction ? new CleanWebpackPlugin() : []),
new webpack.DefinePlugin({
PRODUCTION: isProduction,
}),
new htmlWebpackPlugin({template: './src/index.html'}),
new CopyWebpackPlugin({
patterns: [
{
context: './public',
from: '**/*',
},
]
}),
new CircularDependencyPlugin({
exclude: /node_modules/,
failOnError: true,
}),
],
devServer: { /* blabla*/ },
optimization: {
noEmitOnErrors: true,
},
stats: {
warningsFilter: /export .* was not found in/,
},
}
Related
Hi I'm building a React app with SSR. Server and client part both written in typescript and transpiled separately.
Here the app structure for the better understanding:
Here the simplified webpack configs for the server and client bundles:
// webpack.client.js
module.exports = {
mode: "development",
resolve: {
modules: ["src", "static", "node_modules"],
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
entry: [
"./src/client/index.tsx"
],
output: {
filename: "bundle.js",
path: PUBLIC_PATH,
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: "ts-loader",
}
],
},
]
},
plugins: [
new webpack.DefinePlugin({ IS_SERVER: false })
]
};
Server config looks pretty much the except for the target and externals
//webpack.server.js
const config = {
mode: "development",
resolve: {
modules: ["src", "static", "node_modules"],
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
externals: [webpackNodeExternals()],
target: 'node',
entry: [
"./src/server/index.ts"
],
output: {
filename: "bundle.js",
path: SERVER_BUILD_PATH
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: "ts-loader",
}
],
},
]
},
plugins: [
new webpack.DefinePlugin({ IS_SERVER: true })
]
};
In the server code I got a renderer function which renders React application to string.
// renderer.tsx
import React from "react";
import { renderToString } from "react-dom/server";
import { App } from "client/App";
const html = (app) => `
<html>
<head>
</head>
<body>
<div id="root">${app}</div>
<script src="/public/bundle.js"></script>
</body>
</html>
`;
export async function renderer(req) {
const app = renderToString(<App />);
return html(app);
}
Which then returns to the client by the express server.
//index.ts
app.get("*", async (req, res) => {
const content = await renderer(req);
res.send(content);
});
As you see both parts need to transpile React app. The question is - how can I reuse transpiled client code in the server bundle so that server config only need to transpile index.ts and renderer.tsx?
You can use webpack-merge package to do that
Here is my example
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
const webpack = require("webpack");
module.exports = merge(baseConfig, {
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development'),
'BASE_URL': JSON.stringify('http://localhost:5000/')
}
})
],
watch: true
});
This can be easily done using resolve.alias:
https://webpack.js.org/configuration/resolve/#resolvealias
Simply move the helpers directory up in the app's directory hierarchy and import from it.
In your case, I would also like to redesign the app using a single webpack.config.js file at the root level of your app in which you can combine the client and server configurations using the multi-compiler feature and respect the principle "Do not repeat yourself":
https://stackoverflow.com/a/43689505/2569746
I have a variable in my vars.scss that I want to access from Javascript in root/app/app.vue.
root/app/scss/vars.scss
:export {
cursor: #fff;
}
root/app/app.vue
<template>
<div id="yes">
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import colors from '#/scss/vars.scss';
export default Vue.extend({
mounted() {
console.log(colors.cursor);
},
});
</script>
<style >
</style>
I have read approximately 30 different stackoverflow questions that appear to be dealing with the similar problem of importing variables into the style block of the .vue file, as well as the identical problem of importing the variables directly into the Javascript code. As a result, my webpack.config.js looks like the following:
root/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const env = process.env.NODE_ENV
module.exports = {
entry: './app/index.ts',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'staticfiles')
},
resolve: {
extensions: [ '.ts', '.js', '.vue', '.scss', '.sass'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'#': path.resolve(__dirname, '/app/')
}
},
plugins: [
new HtmlWebpackPlugin(),
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin(),
new VueLoaderPlugin()
],
module: {
rules: [
{
enforce: 'pre',
test: /\.(js|vue|ts)$/,
loader: 'eslint-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
// other vue-loader options go here
}
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.s(a|c)ss$/,
use: [ {
loader: "style-loader",
options: {
sourceMap: env === 'development',
}
}, {
loader: "css-loader",
options: {
sourceMap: env === 'development',
}
}, {
loader: "sass-loader",
options: {
sourceMap: env === 'development',
}
},
'vue-style-loader'],
}]
}
};
I have also tried, in the test: /\.s(a|c)ss$/ section, to put vue-style-loader at the beginning of the array.
I have tried many combinations of filenames when attempting to import the .scss file, such as relative (../scss/vars.scss), removing the extension, using .css as an extension, etc.
The error I get is:
ERROR in /home/Documents/application/app/app.vue.ts
[tsl] ERROR in /home/Documents/application/app/app.vue.ts(10,28)
TS2307: Cannot find module '#/scss/vars.scss'.
My question:
In a project that uses vue-style-loader and vue-loader to build .vue files with webpack, how can I import .scss variables into the <script> portion of a .vue file? (please note - I am NOT attempting to import them into the <style> section of the .vue file)
An example based on my comment:
SCSS fragment:
$foo: #333;
body {
--variable-foo: $foo;
}
And then anywhere in the JavaScript
const value = document.body.style.getPropertyValue("--variable-foo");
console.log(value); // outputs "#333"
I've been googling for a couple hours now and can't seem to resolve my issue.
I have a webpack/React/Typescript/Mobx setup and am attempting to use firebase.
Here is my webpack config: (boilerplate from this repo)
var webpack = require('webpack');
var path = require('path');
// variables
var isProduction = process.argv.indexOf('-p') >= 0;
var sourcePath = path.join(__dirname, './src');
var outPath = path.join(__dirname, './dist');
// plugins
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
module.exports = {
context: sourcePath,
entry: {
main: './main.tsx'
},
output: {
path: outPath,
filename: 'bundle.js',
chunkFilename: '[chunkhash].js',
publicPath: '/'
},
target: 'web',
resolve: {
extensions: ['.js', '.ts', '.tsx'],
// Fix webpack's default behavior to not load packages with jsnext:main module
// (jsnext:main directs not usually distributable es6 format, but es6 sources)
mainFields: ['module', 'browser', 'main'],
alias: {
app: path.resolve(__dirname, 'src/app/'),
assets: path.resolve(__dirname, 'src/assets/')
}
},
module: {
rules: [
// .ts, .tsx
{
test: /\.tsx?$/,
use: [
isProduction
? 'ts-loader'
: {
loader: 'babel-loader',
options: {
babelrc: false,
plugins: ['react-hot-loader/babel']
}
},
'ts-loader'
],
// : ['babel-loader?plugins=react-hot-loader/babel&presets=', 'ts-loader'],
exclude: /node_modules/
},
// css
{
test: /\.css$/,
exclude: /node_modules/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
query: {
modules: true,
sourceMap: !isProduction,
importLoaders: 1,
localIdentName: '[local]__[hash:base64:5]'
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('postcss-import')({ addDependencyTo: webpack }),
require('postcss-url')(),
require('postcss-cssnext')(),
require('postcss-reporter')(),
require('postcss-browser-reporter')({
disabled: isProduction
})
]
}
}
]
})
},
{
test: /\.css$/,
include: /node_modules/,
use: ['style-loader', 'css-loader']
},
// static assets
{ test: /\.html$/, use: 'html-loader' },
{ test: /\.(png|jpg)$/, use: 'url-loader?limit=10000' },
{ test: /\.webm$/, use: 'file-loader' }
]
},
optimization: {
splitChunks: {
name: true,
cacheGroups: {
commons: {
chunks: 'initial',
minChunks: 2
},
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
priority: -10
}
}
},
runtimeChunk: true
},
plugins: [
new WebpackCleanupPlugin(),
new ExtractTextPlugin({
filename: 'styles.css',
disable: !isProduction
}),
new HtmlWebpackPlugin({
template: 'assets/index.html'
})
],
devServer: {
contentBase: sourcePath,
hot: true,
inline: true,
historyApiFallback: {
disableDotRule: true
},
stats: 'minimal'
},
devtool: 'cheap-module-eval-source-map',
node: {
// workaround for webpack-dev-server issue
// https://github.com/webpack/webpack-dev-server/issues/60#issuecomment-103411179
fs: 'empty',
net: 'empty'
}
};
Just by including firebase in my app i relentlessly end up with this error:
Uncaught TypeError: Cannot read property 'navigator' of undefined auth.esm.js?69b5:10
I have tested by including a simple component like so:
import * as React from 'react';
import * as Styles from './styles.css';
import 'app/utils/FirebaseUtil';
interface TestProps {}
export const Test: React.StatelessComponent<TestProps > = () => (
<div className={Styles.root}>
{'Hello World'}
</div>
);
FirebaseUtil:
import * as firebase from 'firebase';
const config = {
apiKey: '**my key here**',
authDomain: '** my domain here **'
};
firebase.initializeApp(config);
export const fbAuth = firebase.auth;
No matter what I seem to do I get the navigator error. Even if i dont export the auth object. As far as I can tell its related to babel-loader adding strict-mode according to this SO question, i think? All other related searches seem to have to do with firebase-ui, which i am not using in any way.
But I have no idea how he manages to turn off strict mode, not to mention the OP is not using typescript and I am using ts-loader in this case. I can't for the life of me figure out how to get it working. Aside from all of this if I do try use the firebase object for auth() for example I get a bunch of warnings from webpack about auth not existing on the firebase object. Totally stumped.
So in case anyone else runs into this problem. It appears it was a package version issue. Im assuming that the package versions specifically included in the boilerplate i used didn't play well with firebase.
I updated typescript, react-hot-loader, and most likely the issue webpack from version 3.0.4 to 4.12.1 and things seem to be working ok now. Also with the updates I now import firebase like so:
import firebase from '#firebase/app';
import '#firebase/auth';
Hope this helps someone.
In my case I fixed this importing functions
import firebase from 'firebase/app'
import 'firebase/functions'
import 'firebase/analytics'
Given the following existing webpack.config.babel.js that's working fine for this application, I would like to add another entry (widget), but if I do so, it requires all external items to be loaded in my HTML page even when I don't need it with my new feature (google, leaflet...) on this part of the application.
widget.js:10488 Uncaught ReferenceError: google is not defined
The plugin & resolve & output existing sections are applying to the new entry js I want to add, so it's good. Only the external is bothering me.
What's the best way to resolve this ? I have very little knowledge of webpack. Thanks.
import path from 'path';
import webpack from 'webpack';
import eslintFormatter from 'eslint-friendly-formatter';
export default (env) => {
const isProd = env ? !!env.release : false;
const isVerbose = env ? !!env.verbose : true;
process.env.NODE_ENV = isProd ? 'production' : 'development';
return {
entry: {
showcase: path.resolve(process.cwd(), 'src/AppBundle/Resources/private/js/showcase/index.js'),
// widget: path.resolve(process.cwd(), 'src/AppBundle/Resources/private/js/widget/index.js'),
},
output: {
path: path.resolve(process.cwd(), 'web/dist/components'),
filename: '[name].js',
publicPath: '/',
},
resolve: {
extensions: ['.js', '.json', '.vue'],
alias: {
Translator: 'node_modules/bazinga-translator/js',
},
},
externals: {
vue: 'Vue',
vuex: 'Vuex',
google: 'google',
leaflet: 'L',
translator: 'Translator',
markerclustererplus: 'MarkerClusterer',
lodash: '_',
routing: 'Routing',
},
module: {
rules: [
{
test: /\.(js|vue)$/,
enforce: 'pre',
include: path.resolve(process.cwd(), 'src/AppBundle/Resources/private/js'),
use: {
loader: 'eslint-loader',
options: {
formatter: eslintFormatter,
},
},
},
{
test: /\.js$/,
include: path.resolve(process.cwd(), 'src/AppBundle/Resources/private/js'),
use: 'babel-loader',
},
{
test: /\.vue$/,
use: 'vue-loader',
},
],
},
plugins: [
// Define environment variables
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
},
}),
// No compile changes on errors
...isProd ? [] : [new webpack.NoEmitOnErrorsPlugin()],
// JavaScript code minimizing
...isProd ? [
// Minimize all JavaScript output of chunks
// https://github.com/mishoo/UglifyJS2#compressor-options
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: isVerbose,
},
}),
] : [],
],
watchOptions: {
aggregateTimeout: 300,
poll: 1000,
},
};
};
Externals is only configuration suppose modules will be exists but webpack does not call it itself. It is possible alredy existed entry loaded togather with all externals and worked fine with no errors. But new entry loaded without loading externals and new entry make (or some else) call not loaded externals. Check you possible have dependencies requiers some externals or your newely added entry make call some of externals (which actually is not loaded in second case).
I am using the package electron-notifications and it relies on a .html and .css file in its assets folder. This assets folder is not included in webpack (1.14.0) though.
I know I should not add a module as an entry point. I have come across a concept called code splitting, but I'm not clear on how that works and if that is what I need to be looking into further. Any advice you can give would be greatly appreciated.
webpack.config.production.js
import path from 'path';
import webpack from 'webpack';
import validate from 'webpack-validator';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import merge from 'webpack-merge';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import BabiliPlugin from 'babili-webpack-plugin';
import baseConfig from './webpack.config.base';
export default validate(merge(baseConfig, {
devtool: 'inline-source-map',
entry: [
'babel-polyfill',
'./app/index'
],
output: {
path: path.join(__dirname, 'app/dist'),
publicPath: '../dist/'
},
module: {
loaders: [
// Extract all .global.css to style.css as is
{
test: /\.global\.css$/,
// loaders: [
loader: ExtractTextPlugin.extract(
'style-loader',
'css-loader?sourceMap'
)
// ]
},
// Pipe other styles through css modules and append to style.css
{
test: /^((?!\.global).)*\.css$/,
// loaders: [
loader: ExtractTextPlugin.extract(
'style-loader',
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
)
},
// Fonts
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream' },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' },
// Images
{
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
loader: 'url-loader'
}
]
},
plugins: [
// https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin
// https://github.com/webpack/webpack/issues/864
new webpack.optimize.OccurrenceOrderPlugin(),
// NODE_ENV should be production so that modules do not perform certain development checks
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new BabiliPlugin(),
new ExtractTextPlugin('style.css', { allChunks: true }),
new HtmlWebpackPlugin({
filename: '../app.html',
template: 'app/app.html',
inject: false
})
],
// https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works
target: 'electron-renderer'
}));
If you want that packages' CSS to be recognised by the webpack, you just add it to the style's(CSS's) loader block, as an include attribute along with "test" and "loader". In the include attribute point it to the node_modules/electron_notification path.
HTML of that package need not be included, since your Single Page Application, has it's own HTML, if needed try to replicate the class names there. But I doubt if you need to do that.