I am using Webpack 4 to build a simple website with an express backend. Now that I am implementing a custom element I am confronted with an issue regarding the shadow DOM.
The issue is as follows: all of my SCSS is being combined into one CSS output file. I need it to remain separate so that the custom element's style can be dynamically added to the shadow DOM, when the element is connected.
I am using extract-css-chunks-webpack-plugin (which I think the issue is with), css-loader, postcss-preset-env and sass-loader to handle all SCSS within the app/site.
All my searching thus far has simply lead me to the exact opposite of what I need (people trying to combine their SCSS).
I understand I could build the custom element separately and then just import it into the project after it has been built but that means managing two building environments and then having to version control across both -- seems like a lot of overhead.
The project's folder structure is as follows:
root
--src/
----assets/
------js/
--------main.js
------scss/
--------main.scss
------web-components/
--------contact-modal.js
--------scss/
----------modal.scss
My current webpack dev config is as follows:
**omitted**
module: {
rules: [{
test: /\.(scss)$/,
use: [{
loader: ExtractCssChunksPlugin.loader,
options: {
hot: true,
}
},
{
loader: 'css-loader',
options: {
sourceMap: true,
}
},
{
loader: 'postcss-loader',
options: {
indent: 'postcss',
plugins: () => postcssEnv(),
sourceMap: 'inline',
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
**omitted**
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: 'src/views/pages/index.ejs',
filename: 'index.html',
}),
new ExtractCssChunksPlugin({
filename: 'assets/css/[name].css',
chunkFilename: 'assets/css/[id].css',
})
]
If there is a better way of using custom elements which I have overlooked I'd appreciate any feedback.
Thank you in advance.
EDIT:
Figure it would be beneficial to state that I am including the modal.scss file with an import in the contact-modal.js file.
I'm assuming modal.scss is importing main.scss? If that's so, then your issue is with the Sass compiler itself, not webpack. Depending on what's in modal.scss, you may want to use #use instead
Edit 1:
If you're adding your compiled css in the shadow DOM in let's say a <style> tag, you can replace the ExtractCSSChunks loader with something like to-string-loader. In your js, import style from "modal.scss"; will have the compiled output in a string that you can use.
In order to solve my issue while still using SCSS (to allow me to use vendor prefixing from postcss-loader) I ended up prefixing my modal scss with a .modal flag so modal.scss became main.modal.scss.
I then edited my webpack config to have two rules for scss files: One which only affected .scss files and one which affected .modal.scss.
Then, in my modal I imported the scss with a normal import style from './main.modal.scss'; to then append it to the shadow DOM in a <style></style> element.
Code is as follows:
New SCSS rule
test: /(\.modal\.scss)$/,
use: [
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [ postcssPresetEnv() ],
sourceMap: 'inline'
}
},
'sass-loader'
]
Modified old SCSS rule
test: /(?<!\.modal)\.scss$/,
use: [{
loader: ExtractCSSChunksPlugin.loader,
options: {
hot: true,
}
},
{
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 3
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
postcssPresetEnv()
],
sourceMap: 'inline'
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
}
}
]
In my contact-modal.js file
import CSS from './scss/main.modal.scss';
// ... omitted ...
connectedCallback() {
// ... omitted ...
const style = document.createElement('style');
style.innerHTML = CSS.toString();
this.shadow.appendChild(style);
// ... omitted ...
}
Related
I have a big javascript (angularjs) application which I have bootstrapped in a Angular 8 project. Now I also have many vendor libraries that are javascript files. For example Kendo libs, JQuery, foundation (zurb).
I have these vendor files, and my app files included in my webpack configuration. Which looks like this:
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
'ang': './chunks/angularjs.ts',
'fou': './chunks/foundation.ts',
'ken': './chunks/kendo.ts',
'vws': './chunks/views.ts',
'dir': './chunks/directives.ts',
'ser': './chunks/services.ts',
'con': './chunks/controllers.ts',
'app': './main.ts',
'toastr': './app-old/scripts/toastr.js'
},
output: {
path: helpers.root('dist/dev'),
publicPath: '/',
filename: '[name].bundle.js'
},
// Currently we need to add '.ts' to the resolve.extensions array.
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
// Add the loader for .ts files.
module: {
rules: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader?keepUrl=true'],
exclude: [/\.(spec|e2e)\.ts$/]
},
{
test: /\.less$/,
loader: 'less-loader', // compiles Less to CSS
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|tff|otf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
// Exposes jQuery for use outside Webpack build
test: require.resolve(path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js')),
use: [{
loader: 'expose-loader',
options: 'jQuery'
}, {
loader: 'expose-loader',
options: '$'
}]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: '../config/index.html'
}),
// new webpack.ProvidePlugin({
// foundation: 'foundation-sites/js/foundation/foundation'
// }),
new webpack.ProvidePlugin({
$: path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js'),
jQuery: path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js'),
"window.jQuery": path.resolve(__dirname, './app-old/scripts/jquery-2.1.4.js')
}),
new webpack.ProvidePlugin({
toastr: 'toastr',
"window.toastr": "toastr"
})
]
}
You see that I have created different entry points, because I want smaller chunks (because of performance).
It compiles perfectly, but when I start up the application the troubles begin. I get al kinds of erros. It keeps telling my that JQuery needs to be loaded before Kendo is being loaded. And the foundation files need don't see the JQuery files.
But I made sure that they are globally available, as you can see in the file.
Not the strange thing.
When I throw everything in 1 TS file and use just only 1 entry point everything runs ok and I have no errors. :-)
But then my bundled file is huge and this is not good for the performance.
So my main question is?
How can I get multiple chunks for my vendor javascript files so that they can see each other?
I am starting to think that I don't need to use the entry points for smaller chunks.
I created new project with vue-cli and I want to use global scss files in my projects to apply global styles. Also I want to use font-families without importing scss file in every component where I want to use it
I found lots of solutions, but none of them help me. I'm new with webpack, so it is hard to understand what exactly goes wrong.
I install loader npm install sass-loader node-sass --save-dev and try to do lots of things with webpack
webpack.config.js
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
],
},
{
test: /\.sass$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader?indentedSyntax'
],
},
{
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: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
src/assets/scss/fonts.scss
#font-face {
font-family: "SuisseIntl";
src: url("../fonts/SuisseIntl.woff") format("woff");
}
#font-face {
font-family: "SuisseIntl-Light";
src: url("../fonts/SuisseIntl-Light.woff") format("woff");
}
#font-face {
font-family: "SuisseIntl-SemiBold";
src: url("../fonts/SuisseIntl-SemiBold.woff") format("woff");
}
$body-bg: red;
I want to be able to use font families in style tag inside every component and I want to be able to import scss files to component like this#import '../assets/scss/fonts'; , but now this cause error.
Can someone help me, please? What should I do to make it work?
vue3 (no idea about vue2)
Late but I maybe still a help for someone.
I assume you have a working vue app already. I normaly create mine with vue ui or cli and use typescript. Many things are preconfigured then. Folder structure looks like this:
/node_modules
/public
/src
/assets
/components
/...
/tests
...
package.json
...
create a vue.config.js (if not exists) in the root (where package.json lies) and put this into it:
module.exports = {
css: {
loaderOptions: {
sass: {
// Or wherever your scss files are. I have a "style.scss" which imports all the others
// # in my case points to src/* and is defined in tsconfig.json. Substitute it with src should do fine
prependData: `
#import "#/assets/scss/style.scss";
`,
},
},
},
};
(optional) put your fonts into (if you want them deliver by yourself)
/assets/fonts/
load your fonts in the style.scss (example)
/* local path, dont forget that ~#/ in the url */
#font-face {
font-family : 'Raleway';
font-style : normal;
src : url('~#/assets/fonts/Raleway-VariableFont_wght.ttf') format('truetype-variations')
}
/* or from google fronts */
#import url(http://fonts.googleapis.com/css?family=Roboto+Slab|Open+Sans:400italic,700italic,400,700);
Double check the font path in the scss file. Vue looks into /src folder. So the rootpath for sass-compiler is inside the src folder and the font-url has to start with ~#/assets/fonts/
To use scss in components, make sure you have both sass-loader and node-sass installed as dev dependencies. This will allow you to use scss styling in components with:
<style lang="scss">
</style>
If you want to include some actual styling (something like your fonts that creates actual styling lines in css), create a scss file and include it in your top-most Vue file (by default something like App.vue).
#import "./some/relative/path/to/your/scss/file.scss";
If you want to include variables, functions or mixins in every component without explicitly having to define the import, make a scss file that serves as your entry point for such configuration, e.g. /scss/config.scss. Make sure that you do not output ANY css rules in this file, because these css rules would be duplicated many times for each component. Instead use the file I mentioned before and import your configuration in there as well.
Then, go to your vue.config.js file and add the following to the object:
css: {
loaderOptions: {
sass: {
data: `
#import "#/scss/config.scss";
`
}
}
}
This will automatically load the config file. If you are still using the old structure, you can get the same behaviour by adding a data option to your own sass-loader:
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
data: '#import "config";',
includePaths: [
path.join(__dirname, 'src/scss') // Or however else you get to your scss folder
]
}
}
],
},
Here is an example that works for me on the latest version of Vue-CLI (#vue/cli 5.0.0-rc.2, vue3) + TS + dart-sass:
I saved my fonts to the assets folder (src/assets/fonts/public-sans-vf.woff2);
Then I created the _fonts.css file in which I describe the rule for the font (src/assets/scss/_fonts.scss);
#font-face {
font-family: "Public Sans";
src: url("~#/assets/fonts/public-sans-vf.woff2")
format("woff2 supports variations"),
url("~#/assets/fonts/public-sans-vf.woff2") format("woff2-variations");
font-style: normal;
font-weight: 400;
font-display: swap;
}
And then I configure the fonts in vue.config.ts
const { defineConfig } = require('#vue/cli-service');
module.exports = defineConfig({
transpileDependencies: true,
css: {
loaderOptions: {
scss: {
additionalData: `
#import "#/assets/scss/_variables.scss";
#import "#/assets/scss/_fonts.scss";
`,
},
},
},
});
I will first include a picture, as it explains the situation the best:
My question is as follows:
The paths circled in red are really odd. Here is an example:
C:/Users/..../Documents/Programming/_actual-programs/html-css-js/github-pages/name.github.io/src/C:/Users/.../Documents/Programming/_actual-programs/html-css-js/github-pages/name.github.io/src/C:/Users/.../Documents/Programming/_actual-programs/html-css-js/github-pages/name.github.io/src/C:/Users/.../Documents/Programming/_actual-programs/html-css-js/github-pages/name.github.io/src/styles.scss
As seen, there are tons of CSS files on the left, under (no domain), and each file has the path repeated multiple times. That does not seem right. I believe it has to do with the source maps for the CSS.
My Webpack setup has sourceMap set to true for every loader that processes CSS (only posting the relevant part):
{
test: /\.s?[ac]ss$/,
exclude: /node_modules/,
use: [
isProduction
? MiniCssExtractPlugin.loader
: {
// creates style nodes from JS strings
loader: 'style-loader',
options: {
sourceMap: true,
// convertToAbsoluteUrls: true
}
},
{
// CSS to CommonJS (resolves CSS imports into exported CSS string in JS bundle)
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 3
// url: false,
// import: false
}
},
{
loader: 'postcss-loader',
options: {
config: {
ctx: {
cssnext: {},
cssnano: {},
autoprefixer: {}
}
},
sourceMap: true
}
},
{
loader: 'resolve-url-loader',
options: {
attempts: 1,
sourceMap: true
}
},
{
// compiles Sass to CSS
loader: 'sass-loader',
options: { sourceMap: true }
}
]
},
If I set one of the sourceMap properties to false (for instance postcss-loader), the paths are no longer duplicated like above.
But according to the various loader documentations, each loader on the chain, from start to end in the Webpack config, must be set to true for the output to be correct.
Any thoughts on what is going on here would be deeply appreciated.
EDIT:
This question has an open issue here: https://github.com/webpack-contrib/css-loader/issues/652
The only thing I have in my entry JS file is:
import $ from 'jquery';
The jQuery JS file has the size of 29.5kb from jsdelivr.
My entry, that only includes jQuery, and nothing else, has the size of 86kb.
webpack.config.js
const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: './src/js/scripts.js',
output: {
publicPath: "./dist/",
path: path.join(__dirname, "dist/js/"),
filename: "bundle.js"
},
watch: true,
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: [
['env', { loose:true, modules:false }],
'stage-2'
],
plugins: [
['transform-react-jsx', { pragma:'h' }]
]
}
},
{
test: /\.pug$/,
use: [
"file-loader?name=[name].html&outputPath=../dist",
"extract-loader",
"html-loader",
"pug-html-loader"
]
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: ['css-loader?url=false', 'sass-loader']
})
},
]
},
resolve: {
alias: {
"TweenMax": path.resolve('node_modules', 'gsap/src/uncompressed/TweenMax.js'),
"TimelineMax": path.resolve('node_modules', 'gsap/src/uncompressed/TimelineMax.js'),
"animation.gsap": path.resolve('node_modules', 'scrollmagic/scrollmagic/uncompressed/plugins/animation.gsap.js'),
}
},
plugins: [
new ExtractTextPlugin('../css/main.css'),
new UglifyJsPlugin({
test: /\.js($|\?)/i
})
],
stats: {
warnings: false
}
};
I should also mention, that going into the output bundle.js it still has the jQuery comments.
jQuery JavaScript Library v3.3.1
https://jquery.com/ ...
Even though I'm calling webpack with the -p argument and have the UglifyJS plugin, but the rest of the file is minified and mangled. Any ideas?
Thanks!
Try to copy and paste minified jquery from your link. It's has size of 86.9 kb.
This link also show that jquery v3 minified file size is also around 80kb.
So you already have correct setup. Maybe your 29.5kb file size is minified+gzipped file.
The 29.5kb file size is definitely the minified+gzipped version as per the link Niyoko posted.
I would also recommend checking out Fuse-Box It brought down our project size from over 1mb to under 200kb (Vendor and App bundles combined). Very easy to get going as well and it is TypeScript first :) It takes the best features from a number of the more popular bundlers and brings them together and builds on those features.
I'm just trying out Webpack 4 and I was wondering if it had a built-in way to manage Scss files since ExtractTextPlugin doesn't work.
In Webpack 4 you also need to use extract-text-webpack-plugin in order to extract text from bundles. The problem is that stable version isn't compatible with the new plugin system. The team is working on it, but in the meantime you need to install the v4.0.0-beta.0 version.
yarn add extract-text-webpack-plugin#next --dev
You can also check this webpack-demo on GitHub with more configs.
The use on webpack.config.js script remains the same:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// ...
module: {
rules: [
{
test: /.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
camelCase: 'dashes',
minimize: true
}
},
{
loader: 'sass-loader'
}
]
})
}
]
},
plugins: [
new ExtractTextPlugin('[name].[chunkhash].css')
]
}