My Vue 3 app references a number of static assets. The static assets are already set up with their own file structure, which happens to be outside of the app source directory. My web server has been configured for this and happily serves files from both the Vue build directory and the static assets directory.
The Vue compiler, however, is not so happy. When it sees a path, it tries to resolve it and copy the asset to the build directory. If I use an absolute path it cannot find the asset, and crashes and dies.
Requirement
I would like the Vue compiler to only output css and js files. I do not need the static files in the build directory. If the Vue compiler sees a reference to a static file - eg:
<style lang="scss">
.hero {
background-image: url('/images/background.jpg');
}
</style>
it should not try to resolve /images/background.jpg and copy it to the build directory; it should assume that I have already put the file where it needs to be.
Current setup
I'm using Vue 3 and Webpack 5. My webpack.config.js is
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' )
const { VueLoaderPlugin } = require( 'vue-loader' )
const path = require( 'path' )
module.exports = {
entry: './src/index.ts',
mode: 'production',
module: {
rules: [ {
test: /\.vue$/,
loader: 'vue-loader'
}, {
test: /\.ts$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [ /\.vue$/ ]
}
}, {
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: "sass-loader",
options: {
additionalData: '#import "#/util/sass/_index.scss";' // import _index.scss to all Vue components
}
}
]
} ]
},
plugins: [
new MiniCssExtractPlugin( {
filename: 'style.css'
} ),
new VueLoaderPlugin(),
],
resolve: {
alias: {
'#': path.resolve( __dirname, 'src' )
},
extensions: [ '.ts', '.scss', '.vue' ],
},
output: {
filename: 'app.js',
path: path.resolve( __dirname, 'build' ),
},
}
You need to tell Webpack to disable asset emission.
By default, asset/resource modules are emitting with [hash][ext][query] filename into output directory.
Since you haven't specified anything concerning asset resources in your webpack config, it will emit a file for each ressource.
As a side note, your vue-loader will not modify absolute paths :
If your static asset URL is specified with an absolute path, Vue will preserve as-is
So, for your concern, make sure to add a rule in your webpack config targeting your asset types. Make sure this rule is the first one run (the last one in your rules array).
//...
module: {
rules: [
//...
{
test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
type: 'asset/resource',
generator: {
emit: false,
},
},
],
},
//...
Here is the webpack reference for more information
Related
I'm trying to build a progressive web app, with support for offline usage.
According to MDN, the way to make PWAs work offline is to add the required resources to a cache in the service worker. This requires that the service worker code knows each of the output files. Ideally, this shouldn't be harcoded, and should be generated by webpack, since it knows what files it generates.
I'm struggling to actually generate this list. From my search, there are two plugins that can generate a json file containing a list of the files - webpack-assets-manifest and webpack-manifest-plugin. I can use these in combination with separate targets to generate a manifest with the page files. But I can't import the manifest, since webpack doesn't actually write the manifest until everything is done.
How can I import a list of files that one entry point generates and use them in another entry point/module?
webpack.config.js:
const path = require('path');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const frontend = {
mode: "development",
entry: {
page:"./src/page/page.tsx",
},
devtool: 'inline-source-map',
output: {
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.html?$|\.png$/,
type: "asset/resource",
generator: {
filename: "[name][ext]",
},
},
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.json$/,
type: "asset/resource",
exclude: /node_modules/,
}
],
},
resolve: {
extensions: [".html", ".tsx", ".ts", ".js"],
},
plugins: [
new WebpackAssetsManifest({
output: "page-files.json",
writeToDisk: true,
}),
]
};
const serviceworker = {
mode: "development",
entry: {
serviceworker: "./src/serviceworker/serviceworker.ts",
},
devtool: 'inline-source-map',
output: {
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.html?$|\.png$/,
type: "asset/resource",
generator: {
filename: "[name][ext]",
},
},
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.json$/,
resourceQuery: /link/,
type: "asset/resource",
exclude: /node_modules/,
},
{
test: /\.json$/,
resourceQuery: /str/,
type: "asset/source",
exclude: /node_modules/,
}
],
},
resolve: {
extensions: [".html", ".tsx", ".ts", ".js"],
},
};
module.exports = [frontend, serviceworker];
serviceworker.ts:
import files from "../../dist/page-files.json?str";
console.log(files);
Error is:
Module not found: Error: Can't resolve '../../dist/page-files.json?str' in '<REDACTED>/src/serviceworker'
(When I build it again, it will find the file from the previous build)
Rather than relying on pre-existing webpack plugins to generate assets, I think you're going to need to write your own plugin for this use case. And if you want that plugin to write the manifest to an entry that itself needs to be bundled/compiled, creating a child compilation in that plugin would be the way to do it.
This is unfortunately not a straightforward task, but you can refer to the source code for the workbox-webpack-plugin's InjectManifest plugin, which more or does what you describe, as inspiration.
Alternatively... you can just use InjectManifest directly, if that meets your use case. While it's part of the Workbox family of libraries, InjectManifest will only actually do two things: process the entry file you pass in as swSrc via a child compilation, and replace the symbol self.__WB_MANIFEST anywhere in that swSrc file with an array of {url: '...', revision: '...'} entries generated based on the assets in the main configuration, filtered by any include/exclude parameters.
So if you don't plan on using Workbox, you can just make use of that self.__WB_MANIFEST value from your own code.
// service-worker.ts
const manifest = self.__WB_MANIFEST || [];
self.addEventListener('install', (event) => {
// Your code to cache the contents of manifest goes here.
});
// webpack.config.js
const {InjectManifest} = require('workbox-webpack-plugin');
module.exports = {
// ...other webpack config...
plugins: [
new InjectManifest({
swSrc: 'src/service-worker.ts',
swDest: 'service-worker.js',
// ...exclude/include config here...
}),
],
};
The reason behind this behavior is that webpack runs these 2 configurations parallelly. By forcing webpack to run sequentially we can fix the problem.
To do serial processing, add module.exports.parallelism = 1; at end of your webpack config.
module.exports = [frontend, serviceworker];
module.exports.parallelism = 1;
Here is the documentation from webpack, https://webpack.js.org/configuration/configuration-types/#parallelism
I have extended default web pack config in Ionic v3 for forcing cache busting.
I am able to fingerprint generated JavaScript artifacts, but I am unable to fingerprint images and JSON files under the assets folder. I took Help from Bundled files and cache-busting.
An excerpt of webpack config.js
module.exports = {
// ...
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].[chunkhash].js',
},
plugins: [
new WebpackChunkHash({algorithm: 'md5'}) // 'md5' is default value
]
}
The above is the approach for fingerprinting JavaScript bundles, and it's working fine. I want to add hashes/fingerprint images and JSON files inside the assets folder. I used the same approach for images also, but it did not work.
I have extended webpack config.js and added a new rule for images. By default webpack directly copies the images and assets to the output folder.
Copy Config.js
module.exports = {
copyAssets: {
src: ['{{SRC}}/assets/**/*'],
dest: '{{WWW}}/assets'
},
copyIndexContent: {
src: ['{{SRC}}/index.html', '{{SRC}}/manifest.json', '{{SRC}}/service-worker.js'],
dest: '{{WWW}}'
},
copyFonts: {
src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'],
dest: '{{WWW}}/assets/fonts'
},
Here images and other assets are directly copied.
I have added a new rule in extended webpack.config.js, but the build process is ignoring it. How do I fix this issue?
Excerpt of webpack config.js
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]',//adding hash for cache busting
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
entire Webpack.config.js
/*
* The webpack config exports an object that has a valid webpack configuration
* For each environment name. By default, there are two Ionic environments:
* "dev" and "prod". As such, the webpack.config.js exports a dictionary object
* with "keys" for "dev" and "prod", where the value is a valid webpack configuration
* For details on configuring webpack, see their documentation here
* https://webpack.js.org/configuration/
*/
var path = require('path');
var webpack = require('webpack');
var ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);
var ModuleConcatPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
var PurifyPlugin = require('#angular-devkit/build-optimizer').PurifyPlugin;
var optimizedProdLoaders = [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.js$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '#angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
]
},
{
test: /\.ts$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '#angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]',
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
},
{
loader: process.env.IONIC_WEBPACK_LOADER
}
]
}
];
function getProdLoaders() {
if (process.env.IONIC_OPTIMIZE_JS === 'true') {
return optimizedProdLoaders;
}
return devConfig.module.loaders;
}
var devConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.ts$/,
loader: process.env.IONIC_WEBPACK_LOADER
},
{
test: /\.(jpg|png)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
}},
]
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin()
],
// Some libraries import Node.js modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
var prodConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: getProdLoaders()
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
new ModuleConcatPlugin(),
new PurifyPlugin()
],
// Some libraries import Node.js modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
module.exports = {
dev: devConfig,
prod: prodConfig
}
Using Webpack 4 you should not need any additional plugins or loaders.
It will give you the naming option [contenthash].
Also, it looks like you have this block nested under the test: .ts block.
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]', // Adding hash for cache busting
outputPath:'assets/imgs',
publicPath:'assets/imgs'
}
}
Ultimately, you can do something like this:
// Copy static assets over with file-loader
{
test: /\.(ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: '[name].[contenthash].[ext]'},
},
{
test: /\.(woff|woff2|eot|ttf|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: 'fonts/[name].[contenthash].[ext]'},
},
{
test: /\.(jpg|gif|png|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: 'images/[name].[contenthash].[ext]'},
}
]
Using [chunkhash] instead of content should still work, and if you're not using webpack4 do that, but otherwise for more information see this issue for an explanation.
For more help, read the long-term caching performance guide from Google and the latest caching documentation from Webpack.
the files copied via CopyPlugin will not pass to loaders.
So even you have correct loader setting with hashname for images, it doesn't work.
but you can see https://github.com/webpack-contrib/copy-webpack-plugin#template
the CopyPlugin provide you a way to specify output name which can be set with hash:
module.exports = {
plugins: [
new CopyPlugin([
{
from: 'src/',
to: 'dest/[name].[hash].[ext]',
toType: 'template',
},
]),
],
};
Eventually, I used gulp for fingerprinting static assets.
Drop Angular Output hashing and build the application.ng build --prod --aot --output-hashing none .
post-build execute a gulp script which would fingerprint all the assets and update the references.
npm i gulp gulp-rev gulp-rev-delete-original gulp-rev-collector
gulpfile.js
const gulp = require('gulp');
const rev = require('gulp-rev');
const revdel = require('gulp-rev-delete-original');
const collect = require('gulp-rev-collector');
// finger priniting static assets
gulp.task('revision:fingerprint', () => {
return gulp
.src([
'dist/welcome/**/*.css',
'dist/welcome/**/*.js',
'dist/welcome/**/*.{jpg,png,jpeg,gif,svg,json,xml,ico,eot,ttf,woff,woff2}'
])
.pipe(rev())
.pipe(revdel())
.pipe(gulp.dest('dist/welcome'))
.pipe(rev.manifest({ path: 'manifest-hash.json' }))
.pipe(gulp.dest('dist'));
});
gulp.task('revision:update-fingerprinted-references', () => {
return gulp
.src(['dist/manifest-hash.json', 'dist/**/*.{html,json,css,js}'])
.pipe(collect())
.pipe(gulp.dest('dist'));
});
gulp.task(
'revision',
gulp.series(
'revision:fingerprint',
'revision:update-fingerprinted-references'));
Add a new script in package.json
"gulp-revision": "gulp revision"
Execute npm run gulp-revision Post-build.
Solving Browser Cache Hell With Gulp-Rev
Using webpack-assets-manifest you can generate a map of asset names to fingerprinted names like so:
{
"images/logo.svg": "images/logo-b111da4f34cefce092b965ebc1078ee3.svg"
}
Using this manifest you can then rename the assets in destination folder, and use the "correct", hash-inclusive src or href in your project.
The fix isn't framework-specific.
I am going through webpack tutorials and it teaches how it is possible to minify and output images that have been imported in main index.js file.
However I would like to minify all image assets, regardless whether they were imported in the index.js or not. Something that was easily done in gulp by having a watch set up on the folder. Does webpack follow same format?
This is my webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules : [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
},
{
loader: 'image-webpack-loader',
}
]
}
]
}
};
No, webpack does not follow the same "logic" as gulp. Webpack """""watches""""" for changes in files that are linked throughout the entire dependency tree. This means that the file you wan't to touch HAS TO BE imported somewhere.
When running my app on webpack dev server, all my image files are working whether as img tags in my index.html or background-image: url()..
Running my project though in production build, I am getting file reference errors that they cannot be found.
GET file:///img/featured.png net::ERR_FILE_NOT_FOUND
GET file:///img/header-img1-1.jpg net::ERR_FILE_NOT_FOUND
I added the copy webpack plugin as I thought this would move all images from my src/img folder to my img folder inside dist.
Should I be using the contentBase option for webpack-dev-server? Or is the copy-webpack-plugin not getting the correct reference? Super confused
Project tree:
- webpack.config.js
- package.json
- .babelrc
- src
- js
- index.js
- ...
- img
- ALL IMAGES LOCATED HERE
- scss
- layout
- landing.scss
- brands.scss
- base
- index.html
inside landing.scss
i have used
background: url('~/img/img-title.png')
same in other files like brands.scss
background: url('~/img/img-title.png')
Which has all worked fine, and I think I've confused myself with how images are referenced with webpack/sass loader, and can't seem to work out how to get the image paths to work for both dev/production, i can only seem to get one working at a time.
production tree:
- dist
- css
- img
- js
- index.html
webpack.config.js:
const path = require('path');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractPlugin = new ExtractTextPlugin({
filename: 'css/main.css'
});
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtPlugin = require('script-ext-html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = (env) => {
const isProduction = env.production === true
return {
entry: './src/js/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
use: 'babel-loader'
},
{
test: /\.css$|\.scss$/,
include: path.resolve(__dirname, 'src'),
use: extractPlugin.extract({
fallback: "style-loader",
use: [
{ loader: 'css-loader', options: { importLoaders: 2, sourceMap: true }},
{ loader: 'postcss-loader', options: { sourceMap: true, plugins: () => [autoprefixer] }},
{ loader: 'sass-loader', options: { sourceMap: true }},
],
})
},
{
test: /\.html$/,
use: ['html-loader']
},
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000,
name: 'img/[name].[ext]',
}
}
]
}
]
},
plugins: [
extractPlugin,
new HtmlWebpackPlugin({
filename: 'index.html',
inject: true,
template: './src/index.html'
}),
new ScriptExtPlugin({
defaultAttribute: 'async'
}),
new CleanWebpackPlugin(['dist']),
new CopyWebpackPlugin([
{from:'src/img',to:'img'}
]),
]
}
}
I think you're using different folder structure on production than on local, i.e. on local, it's just http://localhost:PORT/app, but on prod, it must be similar to http://produrl/Some_Folder/app
Now coming to actual issue - It's your CSS loader.
By default css-loader, has url=true, causing all URLs to be mapped relative to root. Hence this works for you -
background: url('~/img/img-title.png')
but this doesn't
background: url('../../img/img-title.png')
Just set url=false, and you'll be able to provide relative URL and it'll load for all enviorments correctly.
While the accepted answer may works in your specific scenario, I think there is a better solution that do not involve disabling css-loader url() handling and will works better in most of situation.
Tilde ~ problem
When you use ~ to import something in css, css-loader will search for that file inside node_modules. Your images are inside src/img folder so you do not need tilde ~ to import your images.
url() problem
sass-loader doesn't not resolve url() correctly if they are not in the same directory of the entry-file.
In your specific example you import some urls inside src/scss/layout/landing.scss and src/scss/layout/brands.scss but I guess your main scss entry point is inside src/scss folder.
Note: for "main scss entry point" I mean the scss file that you import inside your javascript entry point src/js/index.js
So, in your example, every images imported within a scss file that is not inside src/scss folder will trow an error.
To solve this problem use resolve-url-loader which resolves relative paths in url() statements based on the original source file.
[
{ loader: 'css-loader'}, // set `importLoaders: 3` if needed but should works fine without it
{ loader: 'postcss-loader'},
{ loader: 'resolve-url-loader' }, // add this
{ loader: 'sass-loader',
// sourceMap is required for 'resolve-url-loader' to work
options: { sourceMap: true }
}
]
CopyWebpackPlugin is optional
Based on your configuration you do not need CopyWebpackPlugin because your images are already handle by url-loader.
Note: url-loader doesn't output your images but inline them, images are outputted by file-loader when the file is greater than the limit (in bytes).
file-loader will output your images in dist/img folder, because you set name: 'img/[name].[ext]', and you set output.path: path.resolve(__dirname, 'dist').
Hey I think your issue is just in how you're referencing the image in your scss.
Based on your folder structure it should be:
background: url('../../img/img-title.png')
You could modify the "publicPath" URL (which is relative to the server root) to match the file structure.
//webpack.config.js
...
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "assets/images/",
publicPath: "/other-dir/assets/images/"
}
}
]
}
]
}
I'm using a Yeoman project template called "aspnetcore-spa", which is an ASP.net core 1 template working in conjunction with major SPA frameworks (Angular2 and React).
I created a project with Angular2.The biolerplate's code works fine and there is no problem. Once I add Sass loader to webpack.config.js and make a reference to the Sass file from any angular file.
In webpack.config.js :
var isDevBuild = process.argv.indexOf('--env.prod') < 0;
var path = require('path');
var webpack = require('webpack');
var nodeExternals = require('webpack-node-externals');
var merge = require('webpack-merge');
var allFilenamesExceptJavaScript = /\.(?!js(\?|$))([^.]+(\?|$))/;
// Configuration in common to both client-side and server-side bundles
var sharedConfig = {
resolve: { extensions: [ '', '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
loaders: [
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts', query: { silent: true } },
{ test: /\.scss$/,include:/ClientApp/, loaders: ["style", "css", "sass"] },
{ test: /\.html$/,include: /ClientApp/, loader: 'raw' },
{ test: /\.css$/, loader: 'to-string!css' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url', query: { limit: 25000 } }
]
}
};
// Configuration for client-side bundle suitable for running in browsers
var clientBundleOutputDir = './wwwroot/dist';
var clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot-client.ts' },
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin()
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
var serverBundleConfig = merge(sharedConfig, {
entry: { 'main-server': './ClientApp/boot-server.ts' },
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map',
externals: [nodeExternals({ whitelist: [allFilenamesExceptJavaScript] })] // Don't bundle .js files from node_modules
});
module.exports = [clientBundleConfig, serverBundleConfig];
In my component :
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-wine',
template: require('./wine.component.html'),
styles: require('./wine.component.scss')
})
export class WineComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
I have already installed npm packages pertinent to sass loader :
npm install node-sass sass-loader --save-dev
I have checked the main-server.js file in wwwroot/dist folder which is the result of webpack bundling, I saw that the .scss file is loaded and they styles are processed correctly. Once I run the app though, shows this exception which is coming from the server side rendering side:
An unhandled exception occurred while processing the request.
Exception: Call to Node module failed with error: ReferenceError: window is not defined at E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:573:31 at E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:568:48 at module.exports (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:590:69) at Object. (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:526:38) at webpack_require (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:20:30) at E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:501:22 at Object.module.exports (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:506:3) at webpack_require (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:20:30) at Object. (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:129:25) at webpack_require (E:\Dev\MyApp\MyAppCore\src\MyApp.Web\ClientApp\dist\main-server.js:20:30)
It's obviously because of the webpack's server-side rendering, as it's running the code on Node.js side (through ASP.net Core's Javascript Services) and there is a code that is coupled with the DOM window object which is not valid on node.
Any clues?
I managed to fix the problem, here's the web.config.js bit:
(Notice the loaders for .scss files)
module: {
loaders: [
{ test: /\.ts$/, include: /ClientApp/, loader: 'ts', query: { silent: true } },
{ test: /\.scss$/,include:/ClientApp/, loaders: ["to-string", "css", "sass"] },
{ test: /\.html$/,include: /ClientApp/, loader: 'raw' },
{ test: /\.css$/, loader: 'to-string!css' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, loader: 'url', query: { limit: 25000 } }
]
}
And in the Angular component I changed the styles to this :
(Passed an array of required css files rather than a single css file)
#Component({
selector: 'app-wine',
template: require('./wine.component.html'),
styles: [require('./wine.component.scss')]
})