Webpack - how to tell not compiling a js file for a specific entry point - npm

Context
I am actually managing some dependencies via npm. Then I wrote a webpack.config.js to handle my imports :
Bundle Jquery + popper + Bootstrap.js
Bundle codemirror with my modules needs (xml autocomplete and so on)
...
Collect fonts from the fontawesome node_module
I am using django, so there is a collectstatic command which is helping me to collect all scripts, fonts, styles etc. to the folder I want. Everything is working great :
My bundled scripts are available
And for style sheets, I'm using relative imports to the 'vendor' node_module.
I am capable to get fonts to node_modules, and deliver it to the correct folder as expected.
Problem
I'm new to webpack world, and for sure I'm not using it as it should be, but it's working.
However when I run to compile my stuffs, an extra file is generated fontawesome.js in my fonts folder, is it possible to avoid this unexpected behavior.
What am I missing ?
It is not a big deal as long as I'm not importing this file, but I don't want to pollute my repo.
Edit
I updated the code of my webpack config.
Well, I figured out that the js file was generated from the filename in my entry point. But I don't want this file :)
wepback.config.js
"webpack": "^5.6.0",
var path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // Extract css in its own file
const webpack = require('webpack');
const path_modules = path.resolve(__dirname, 'node_modules');
const base_path = path.resolve(__dirname, 'src');
const dist_static = path.resolve(__dirname, 'webpacked_src');
module.exports = {
entry: {
bootstrap: {
import: [
base_path + '/bootstrap/bootstrap-bundle.js',
//base_path + '/bootstrap/test.scss'
],
filename: 'js_packed/[name].js',
},
fontawesome: {
import: [
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.eot',
//path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.svg',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.woff',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.eot',
//path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.svg',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.woff',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.eot',
//path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.svg',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.woff',
path_modules + '/#fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2',
],
filename: 'fonts/[name].js'
},
codemirror: {
import: [
base_path + '/codemirror/code-mirror.js',
path_modules + '/codemirror/lib/codemirror.css',
path_modules + '/codemirror/theme/monokai.css', // Import the theme style --> Little selection midnight / material-darker / material-ocean / monokai (sublimetext)
path_modules + '/codemirror/addon/hint/show-hint.css',
],
filename: 'js_packed/[name].js',
},
vue: {
import: [
base_path + '/vue/vue.js',
],
filename: 'js_packed/[name].js',
},
},
output: {
path: dist_static
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
autoprefixer = require('autoprefixer'),
new MiniCssExtractPlugin({
filename: "css_packed/[name].css", // change this RELATIVE to the output.path
}),
],
module: {
rules: [
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // instead of style-loader
'css-loader'
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
{
loader: 'file-loader',
options: {outputPath: 'css/', name: '[name].min.css'}
},
'sass-loader'
]
}
],
},
};
fontawesome.js
And the content of the generated file, if it can help webpack experts :
(() => {
"use strict";
var t = {};
t.g = function () {
if ("object" == typeof globalThis) return globalThis;
try {
return this || new Function("return this")()
} catch (t) {
if ("object" == typeof window) return window
}
}(), (() => {
var r;
t.g.importScripts && (r = t.g.location + "");
var e = t.g.document;
if (!r && e && (e.currentScript && (r = e.currentScript.src), !r)) {
var p = e.getElementsByTagName("script");
p.length && (r = p[p.length - 1].src)
}
if (!r) throw new Error("Automatic publicPath is not supported in this browser");
r = r.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/"), t.p = r + "../"
})(), t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p, t.p
})();

Unfortunately, this is a bug in webpack: https://github.com/webpack/webpack/issues/11671
In the meantime, it's straight forward to add a custom webpack plugin that will remove these files. Here's what I'm using to delete all JS files (because I'm using a CSS file as my entry):
plugins: [
{
// Delete empty JS files; work around for https://github.com/webpack/webpack/issues/11671
apply: (compiler) => {
compiler.hooks.afterEmit.tap('DeleteJSFilesPlugin', (compilation) => {
const iter = compilation.emittedAssets.entries();
for (const [key] of iter) {
if (key.match(/.*\.js$/)) {
fs.unlinkSync(path.join(compilation.outputOptions.path, key));
}
}
});
}
}
]

Related

Commitlint - Allow '/' in scope-enum

In my Angular project, I want to extend #commitlint/config-conventional with some pre-defined scopes.
The Angular project has a library for UI components (generated via ng generate library) and a default app which consumes the UI library.
In commitlint.config.js I've added the following lines:
module.exports = {
extends: ['#commitlint/config-conventional'],
rules: {
'scope-enum': [
2,
'always',
[
'ui-components',
'ui-components/badge',
'ui-components/button',
'ui-components/tooltip',
'core',
'account',
'plugins',
'settings',
'projects',
'shared',
'styles'
]
]
}
};
However, when I try to commit something with the scope: 'ui-components/tooltip':
fix(ui-components/tooltip): fix border
I get a commitlint error, saying that:
⧗ input: fix(ui-components/tooltip): fix border
✖ scope must be one of [ui-components, ui-components/badge, ui/button, ui-components/tooltip, core, account, plugins, settings, projects, shared, styles] [scope-enum]
✖ found 1 problems, 0 warnings
Unfortunately slashes aren't allowed in scopes.
To get around this I replace / with two dashes (--).
I wrote a script to grab subfolders and return an array:
https://gist.github.com/trevor-coleman/51f1730044e14081faaff098618aba36
[
'ui-components',
'ui-components--badge',
'ui-components--button',
'ui-components--tooltip',
...
]
According to source code, Commitlint use / for multiple scopes.
It means, you can commit like fix(core/account): fix border but you can't commit fix(ui-components/tooltip): fix border because you need to add tooltip in to your scopes first.
Here is source code: https://github.com/conventional-changelog/commitlint/blob/master/%40commitlint/rules/src/scope-enum.ts
Also, it is mentioned in here: https://github.com/conventional-changelog/commitlint/blob/master/docs/concepts-commit-conventions.md#multiple-scopes
You can write your own custom plugin to check scopes, I had the same issue, so I wrote one to solve this problem, see example commitlint.config.js below:
module.exports = {
extends: ["#commitlint/config-conventional"],
rules: {
"enhanced-scope-enum": [
2,
"always",
[
"ui-components",
"ui-components/badge",
"ui-components/button",
"ui-components/tooltip",
"core",
"account",
"plugins",
"settings",
"projects",
"shared",
"styles",
],
],
},
plugins: [
{
rules: {
"enhanced-scope-enum": (parsed, when = "always", value = []) => {
if (!parsed.scope) {
return [true, ""];
}
// only use comma sign as seperator
const scopeSegments = parsed.scope.split(",");
const check = (value, enums) => {
if (value === undefined) {
return false;
}
if (!Array.isArray(enums)) {
return false;
}
return enums.indexOf(value) > -1;
};
const negated = when === "never";
const result =
value.length === 0 ||
scopeSegments.every((scope) => check(scope, value));
return [
negated ? !result : result,
`scope must ${negated ? `not` : null} be one of [${value.join(
", "
)}]`,
];
},
},
},
],
}

Vue.js: Is it possible to do "Conditional compilation" with a Vue project?

In for example Swift/iOS development, it's possible to differentiate builds for different environments with "flags" such as:
#if STAGING
// one set of logic here
#endif
#if PRODUCTION
// another set of logic here
#endif
Is it possible to achieve the same with a Vue.js project, and how would we go about doing it? I am aware of makes different routes conditionally available for different roles (which is also quite neat), but I am optimally looking for the option to differentiate on a source code level.
Hope someone has some great insights! It could include:
How to exclude parts of a file (such as the #if STAGING above) from a build target
How to exclude entire files from a build target
etc.
you have the ability to use this syntax
if(process.env.NODE_ENV === 'production') {
console.log("this is the prod env!!!!!!!!!!");
config.output.path = path.resolve(__dirname, "dist");
}
make sure that when you run the script with the correct env's for each environment (local, dev, staging, prod etc ..) :D
just change the vue-loader output.
the source code
<template v-if="process.env.NODE_ENV === 'development'">
development only
</template>
default output
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
_vm.process.env.NODE_ENV === "development"
? [_vm._v(" development only ")]
: _vm._e(),
_c("router-view")
],
2
)
}
just use regex to replace _vm.process.env. by process.env is ok.
// webpack.config.js
module: {
rules: [{
// must set post
enforce: 'post',
test: /\.vue$/,
use: [{
loader: './myLoader'
}]
}]
}
// myLoader.js
module.exports = function (source, map) {
if (source.indexOf('_vm.process.env') > -1) {
source = source.replace(/_vm.process.env/g, 'process.env')
}
this.callback(
null,
source,
map
)
}
Final the vue-loader result change
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
// change to true
true
? [_vm._v(" development only ")]
: undefined,
_c("router-view")
],
2
)
}

Load CSS from a dynamically imported ES6 module

My project is being built with Webpack via Laravel Mix. I want to dynamically import an ES6 module that itself imports other modules and a stylesheet. Here is the dynamically imported module (loadJQueryTextillate.js):
import style from 'animate.css/animate.css';
import 'letteringjs';
import 'textillate';
style.use();
export default () => {
};
Here is the module that dynamically imports loadJQueryTextillate.js (animatedText.js):
import isInViewport from './isInViewport';
function maybeAnimateText( elem ) {
const $el = $( elem );
let bounding,
el_html,
el_lines,
in_viewport = $el.data( 'in-viewport' ) || false;
const viewport_height = window.innerHeight || document.documentElement.clientHeight;
if ( $el.hasClass( 'opaque' ) ) {
bounding = elem.getBoundingClientRect();
if ( in_viewport && !isInViewport( elem ) && ( bounding.top > viewport_height ) ) { // Element scrolled off screen
in_viewport = false;
$el.removeClass( 'opaque' ).find( 'ul.texts' ).remove().end().text( $.trim( $el.text() ) );
} else if ( isInViewport( elem ) ) {
in_viewport = true;
}
$el.data( 'in-viewport', in_viewport );
return;
} else if ( !isInViewport( elem ) ) {
return;
}
el_html = $el.html();
el_lines = el_html.split( /<br\s*\/?>/ );
$.each( el_lines, function( key, line ) {
el_lines[ key ] = $.trim( line );
} );
el_html = '<span class="line">' + el_lines.join( '</span><span class="line">' ) + '</span>';
import( /* webpackChunkName: "scripts/jQuery.textillate" */ './loadJQueryTextillate' ).then( () => {
$el.html( el_html ).addClass( 'opaque' ).children( '.line' ).textillate( {
in : {
effect : $el.data( 'in-effect' ) || 'fadeInLeft',
delay : $el.data( 'delay' ) || 12,
},
} );
} );
}
export default () => {
const $els = $( '.tlt' );
if ( 0 === $els.length ) {
return false;
}
$els.each( function( index, elem ) {
maybeAnimateText( elem );
} );
return true;
};
Here is the JS entry script (app.js):
window.$ = window.jQuery = require( 'jquery' );
import 'bootstrap';
import checkAnimatedText from './modules/animatedText';
$( window ).on( 'load', () => {
checkAnimatedText();
} );
Finally, here is the Laravel Mix config script (webpack.mix.js):
const mix = require( 'laravel-mix' );
require( 'laravel-mix-versionhash' );
// Public path helper
const publicPath = path => `${mix.config.publicPath}/${path}`;
// Source path helper
const src = path => `resources/assets/${path}`;
// Public Path
mix
.setPublicPath( './dist' )
.setResourceRoot( `/wp-content/themes/magnetar/${mix.config.publicPath}/` )
.webpackConfig( {
module : {
rules : [ {
test : /animate\.css$/,
use : [ {
loader : "style-loader/useable",
}, { loader : "css-loader" } ],
} ],
},
output : { publicPath : mix.config.resourceRoot },
} );
// Browsersync
mix.browserSync( 'magnetar.localhost' );
// Styles
mix.sass( src`styles/app.scss`, 'styles' );
// Assets
mix.copyDirectory( src`images`, publicPath`images` )
.copyDirectory( src`fonts`, publicPath`fonts` );
// JavaScript
mix.js( src`scripts/app.js`, 'scripts' );
//.extract();
// Autoload
/*mix.autoload( {
jquery : [ '$', 'window.jQuery' ],
} );*/
// Source maps when not in production.
mix.sourceMaps( false, 'source-map' );
// Hash and version files in production.
mix.versionHash( { length : 16 } );
Compiler output:
ERROR in ./node_modules/animate.css/animate.css (./node_modules/css-loader??ref--6-1!./node_modules/postcss-loader/src??ref--6-2!./node_modules/style-loader/useable.js!./node_modules/css-loader!./node_modules/animate.css/animate.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
SyntaxError
(1:1) Unknown word
> 1 | var refs = 0;
| ^
2 | var dispose;
3 | var content = require("!!../css-loader/index.js!./animate.css");
EDIT: Updated contents of loadJQueryTextillate.js, webpack.mix.js and compiler output.
You can try style-loader/useable to dynamically load css file. In your script code, you should use style.use() to make style useable or use style.unuse() to make style disable.
The following code shows how you should do to use style-loader/useable.
webpack.config.js
{
module: {
rules: [
{
test: /\.css$/,
exclude: /\.useable\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
],
},
{
test: /\.useable\.css$/,
use: [
{
loader: "style-loader/useable"
},
{ loader: "css-loader" },
],
},
],
},
}
file you want to dynamically load animate.css
import style form './animate.css';
// make aniamte.css useable
style.use();
// make animate.css disable
style.unuse();

How do I integrate PostCSS into Broccoli angular-cli Build?

It seems like this build script is running but the CSS that is output is not minified or auto prefixed. I am attempting to compile SASS, then run the output through Post CSS with Broccoli and the angular-cli. I figure maybe some ember-cli folks could help as well. What am I doing wrong?
The build outputs this in terminal:
Slowest Trees | Total
----------------------------------------------+---------------------
BroccoliTypeScriptCompiler | 1274ms
vendor | 502ms
PostcssFilter | 465ms
but the CSS is the same as if it were output from SASS, not Post CSS.
Here is my angular-cli-build.js:
'use strict';
/* global require, module */
const Angular2App = require('angular-cli/lib/broccoli/angular2-app');
const compileSass = require('broccoli-sass');
const compileCSS = require('broccoli-postcss');
const cssnext = require('postcss-cssnext');
const cssnano = require('cssnano');
const mergeTrees = require('broccoli-merge-trees');
const Funnel = require('broccoli-funnel');
const _ = require('lodash');
const glob = require('glob');
var options = {
plugins: [
{
module: cssnext,
options: {
browsers: ['> 1%'],
warnForDuplicates: false
}
},
{
module: cssnano,
options: {
safe: true,
sourcemap: true
}
}
]
};
module.exports = function(defaults) {
let sourceDir = 'src';
let appTree = new Angular2App(defaults, {
sourceDir: sourceDir,
sassCompiler: {
includePaths: [
'src/style'
]
},
vendorNpmFiles: [
'systemjs/dist/system-polyfills.js',
'systemjs/dist/system.src.js',
'zone.js/dist/*.js',
'es6-shim/es6-shim.js',
'reflect-metadata/*.js',
'reflect-metadata/*.js.map',
'rxjs/**/*.js',
'#angular/**/*.js',
'rxjs/**/*.js.map',
'#angular/**/*.js.map',
'd3/d3.js',
'three/build/three.js',
'three/examples/js/postprocessing/*.js',
'three/examples/js/shaders/*.js'
]
});
let sass = mergeTrees(_.map(glob.sync('src/**/*.scss'), function(sassFile) {
sassFile = sassFile.replace('src/', '');
return compileSass(['src'], sassFile, sassFile.replace(/.scss$/, '.css'));
}));
let css = compileCSS(sass, options);
return mergeTrees([sass, css, appTree], { overwrite: true });
};
It was the order of operations, my trees were not overwriting appTree!
This works!
return mergeTrees([appTree, sass, css], { overwrite: true });

Include npm dependency in karma

How can I include a dependency in package.json (not a devDependency) in karma?
I can include the file in node_modules/<dependency>, but I was looking for something more generic, including the main file.
I did not find any other better way to include them in karma.conf.js than
module.exports = function(config) {
var cfg = {
frameworks: ["jasmine", "commonjs"]
, files: [
{pattern: "node_modules/" + deps + "/*.js", included: true, watched: false}
, ...
]
...
};
cfg.preprocessors["node_modules/" + deps + "/**/*.js"] = ["commonjs"];
config.set(cfg);
};