Why is Vue undefined in my webpack-bundled app after trying to import it as a client-side library - vue.js

I have been having an issue for 2 days and finally isolated it. Importing Vue and VueRouter gives me undefined in my frontend code. This is because the output from webpack, using the externals property to load client-side libraries, checks for module.__esModule when you import an externally loaded library, and if true then it returns module.default. In this case Vue.__esModule and VueRouter.__esModule are true while Vue.default and VueRouter.default are undefined. I'm not even sure who to file a bug with, or whether there is something I can add to webpack.config.js to make this work as before. It could be Vue for including __esModule on their global builds. Or it could be something in the internals of webpack.
Here is a section of my package.json
"devDependencies": {
"#babel/core": "^7.17.0",
"#babel/preset-env": "^7.16.11",
"#vue/compiler-sfc": "^3.2.30",
"babel-loader": "^8.2.3",
"css-loader": "^6.6.0",
"mini-css-extract-plugin": "^2.5.3",
"node-sass": "^7.0.1",
"sass-loader": "^12.4.0",
"vue-loader": "^17.0.0",
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2"
},
"dependencies": {
"vue": "^3.2.29"
}
And webpack.config.js
const VueLoader = require('vue-loader');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: 'production',
stats: 'errors-warnings',
entry: [
'./src/app.js',
],
output: {
filename: 'compiled.js',
path: __dirname + '/js',
},
optimization: {
minimize: true,
},
performance: {
hints: 'warning',
maxEntrypointSize: 250000, // JS output 250 kB
maxAssetSize: 250000, // CSS output 250 kB
},
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
use: {
loader: 'babel-loader',
options: {
presets: [
['#babel/preset-env', {targets: '>1%'}],
],
},
},
},
{
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader, // add support for `import 'file.scss';` in JS
{
loader: 'css-loader',
options: {
url: false, // whether to resolve urls; leave urls in the code as written
},
},
{
loader: 'sass-loader',
options: {
sassOptions: {
includePaths: [
//__dirname + '/bower_components/bootstrap-sass/assets/stylesheets',
],
},
},
},
],
},
],
},
plugins: [
new VueLoader.VueLoaderPlugin(),
new MiniCssExtractPlugin({
// Output destination for compiled CSS
filename: '../css/compiled.css',
}),
],
};
And index.html loads Vue, VueRouter, Vuex, and then my bundled webapp:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.29/vue.runtime.global.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/4.0.2/vuex.global.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/4.0.12/vue-router.global.js"></script>
<script>console.log('healthcheck', Vue, Vuex, VueRouter);</script>
<script src="/js/compiled.js"></script>
Then in my frontend code, bundled with webpack:
import Vue from "vue";
import Vuex from "vuex";
import VueRouter from "vue-router";
console.log('client', Vue, Vuex, VueRouter);
Logs healthcheck {...} {...} {...} in the HTML and then the compiled app logs client undefined, {...}, undefined (Vuex is defined because Vuex.__esModule is undefined)
Any ideas what to do?

Answered here https://github.com/vuejs/core/issues/5380
Vue 3 only supports named imports, like import {createApp} from 'vue';

Related

Vue 3 vendor bundle is bloated with #babel/parser/lib when vue.esm-bundler.js is used in webpack

I created a Vue 3 project and using webpack for bundling the package. Since I have in-DOM templates, I cannot go with the default #runtime-dom. So I have aliased Vue to point to vue.esm-bundler.js.
The issue I am facing is that, when I take a prod build, my vendor bundle is bloated with #babel/parser/lib.
Sample project to reproduce this issue is available here
Steps to follow:
npm install
npm run bundle
Open dist folder and see the Webpack bundle analyser report.
For ease of config, pasting the configs below.
webpack.config.js
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const { VueLoaderPlugin } = require("vue-loader");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = (env, options) => {
const devMode = options.mode != "production";
return {
context: path.resolve(__dirname, "src"),
entry: {
"vue-bundle": "./entry/main.js",
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
chunkFilename: "[name].js",
},
module: {
rules: [
{
test: /\.js$/,
exclude: [/node_modules/],
use: {
loader: "babel-loader",
options: {
presets: [
[
"#babel/preset-env",
{
targets: [">25%"],
debug: true,
corejs: "3.6.5",
useBuiltIns: false,
},
],
],
},
},
},
{
test: /\.vue$/,
use: "vue-loader",
},
],
},
plugins: [
new CleanWebpackPlugin(),
new VueLoaderPlugin(),
new BundleAnalyzerPlugin({
openAnalyzer: false,
analyzerMode: "static",
reportFilename: "webpack_bundle_analyser_report.html",
defaultSizes: "gzip",
}),
],
optimization: {
mangleWasmImports: true,
removeAvailableModules: true,
sideEffects: true,
minimize: devMode ? false : true,
minimizer: [
new TerserPlugin({
test: /\.js(\?.*)?$/i,
exclude: /\/node-modules/,
parallel: 4,
extractComments: false,
}),
],
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendor-bundle",
chunks: "all",
},
},
},
},
devtool: devMode ? "eval-cheap-source-map" : false,
resolve: {
extensions: [".ts", ".js", ".vue", ".json"],
alias: {
vue: "vue/dist/vue.esm-bundler.js"
},
},
};
};
package.json
{
"name": "testPro",
"version": "1.0.0",
"private": true,
"scripts": {
"bundle": "webpack --mode=production --config webpack.config.js",
"bundle-dev": "webpack --mode=development --config webpack.config.js"
},
"devDependencies": {
"#babel/core": "^7.12.3",
"#babel/preset-env": "^7.12.1",
"#babel/preset-typescript": "^7.12.1",
"#vue/compiler-sfc": "^3.0.2",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.6.5",
"regenerator-runtime": "^0.13.7",
"terser-webpack-plugin": "^5.0.3",
"vue-loader": "^16.0.0-beta.4",
"webpack": "^5.3.0",
"webpack-bundle-analyzer": "^3.9.0",
"webpack-cli": "^4.1.0"
},
"dependencies": {
"vue": "^3.0.2"
}
}
Entry file main.js
import { createApp } from 'vue';
import App from '../App.vue';
createApp(App).mount('#app');
Not able to get what I am missing.
I strongly believe it is a bug in Vue 3 so I submitted a bug report - you can track it here
...I reproduced it myself using Vue CLI just to eliminate the chance the problem is in your Webpack config
You have 2 options to workaround this issue:
If you don't need to release right now, just work on your project and wait for a fix (I'm pretty sure it will be fixed - Vue builds for a browser which include compiler does not depend on #babel/parser so it's clear Vue don't need it to work correctly inside browser)
Don't use in-DOM templates and template option (string templates) - put everything in .vue files, <template></template> blocks - Runtime + Compiler vs. Runtime-only. Then you don't need a build with compiler...
EDIT: removed the part about missing process.env.NODE_ENV as --mode param to Webpack CLI does exactly that...

vue.runtime.esm-browser.js does not render Vue 3 components

I created a vue 3 project using Vue cli. I am using a webpack config to manage my build. When I point my vue bundle to vue.runtime.esm-browser.js, then I get a warning in browser console. "[Vue warn]: Component provided template option but runtime compilation is not supported in this build of Vue. Use "vue.esm-browser.js" instead."
When I checked the docs, it was mentioned as "vue-loader" plugin converts the html template to render functions. Looks like I am missing something which is needed to webpack.
Entry file : main.js
import { createApp } from "vue";
import corecomponentA from "../core/components/corecomponentA.vue";
createApp({
components: {
"core-component-a": corecomponentA,
},
}).mount("#app");
Webpack.config.js
var path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const WebpackBar = require("webpackbar");
module.exports = (env, options) => {
const devMode = options.mode != "production";
return {
entry: {
"vue-bundle-store": "./src/entry/main.js",
},
output: {
path: path.resolve(
__dirname,
"./../ui.clientlibs/src/js/"
),
filename: "[name].js",
chunkFilename: "[name].js",
publicPath: process.env.BASE_URL,
},
module: {
rules: [
{
enforce: "pre",
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
},
{
test: /\.vue$/,
loader: "vue-loader",
},
{
test: /\.js$/,
loader: "babel-loader",
exclude: "/node_modules/",
query: {
presets: ["#babel/preset-env"],
},
},
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: { babelrc: true },
},
{
loader: "ts-loader",
options: { appendTsSuffixTo: [/\.vue$/] },
},
],
},
],
},
stats: {
colors: true,
},
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendor-bundle",
chunks: "all",
},
},
},
minimizer: !devMode
? [
new UglifyJsPlugin({
sourceMap: false,
uglifyOptions: {
chunkFilter: (chunk) => {
if (chunk.name === "vendor-bundle") {
return false;
}
return true;
},
compress: {
drop_console: true,
},
mangle: {
reserved: ["vueIns", "args", "el"],
},
},
}),
]
: [],
},
devtool: "source-map",
plugins: [
new CleanWebpackPlugin(),
new VueLoaderPlugin(),
new WebpackBar(),
new BundleAnalyzerPlugin({
analyzerPort: 4000,
openAnalyzer: false,
analyzerMode: "static",
}),
] ,
resolve: {
extensions: [".ts", ".js", ".vue", ".json"],
alias: {
vue: devMode ? "vue/dist/vue.runtime.esm-browser.js" : "vue/dist/vue.runtime.esm-browser.prod.js"
}
}
};
};
coreComponentA.vue
<script lang="ts">
import { h, ref, reactive } from "vue";
export default {
setup() {
const str = ref("Core component B");
const object = reactive({ foo: "bar" });
return () => h("div", [str.value, object.foo]);
}
};
</script>
package.json
{
"name": "vue3.test",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"analyze-bundle": "webpack-bundle-analyzer stats.json",
"bundle": "webpack --mode=production --env.production --config webpack.config.js",
"bundle-dev": "webpack --mode=development --env.production=false --config webpack.config.js",
"stats": "webpack --mode=production --env.production --config webpack.config.js --profile --json > stats.json"
},
"dependencies": {
"vue": "^3.0.2"
},
"devDependencies": {
"#types/jest": "^24.0.19",
"#typescript-eslint/eslint-plugin": "^2.33.0",
"#typescript-eslint/parser": "^2.33.0",
"#vue/cli-plugin-babel": "~4.5.0",
"#vue/cli-plugin-eslint": "~4.5.0",
"#vue/cli-plugin-typescript": "~4.5.0",
"#vue/cli-plugin-unit-jest": "~4.5.0",
"#vue/cli-service": "~4.5.0",
"#vue/compiler-sfc": "^3.0.2",
"#vue/eslint-config-prettier": "^6.0.0",
"#vue/eslint-config-typescript": "^5.0.2",
"#vue/test-utils": "^2.0.0-0",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.6.5",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^7.0.0-0",
"html-webpack-plugin": "^3.2.0",
"prettier": "^1.19.1",
"typescript": "~3.9.3",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-jest": "^5.0.0-0",
"vue-loader": "^16.0.0-beta.8",
"webpack-cli": "^3.3.10",
"webpackbar": "^4.0.0"
}
}
babel.config.js
module.exports = {
ignore: [/\/core-js/],
presets: [
[
"#babel/preset-env",
{ modules: false, useBuiltIns: "usage", corejs: "3.6.5" },
],
],
overrides: [
{
test: "./node_modules",
sourceType: "unambiguous",
},
],
};
Usage of my component in a html file
<div id="app">
<core-component-a></core-component-a>
</div>
The component is not rendered in browser. Instead the below message is displayed.
VM211871:1 [Vue warn]: Component provided template option but runtime compilation is not supported in this build of Vue. Use "vue.esm-browser.js" instead.
at <App>
vue-loader converts the html template to render function only in SFC's (Sinle File Components) - .vue files (as you can tell from vue rule in Webpack config) - and only templates provided in <template></template> block of SFC
But you have a template in your HTML file - content of <div id="app"> is essentially Vue template. Runtime + Compiler vs. Runtime-only
Docs vue.esm-bundler.js: includes the runtime compiler. Use this if you are using a bundler but still want runtime template compilation (e.g. in-DOM templates or templates via inline JavaScript strings - component template option).
Also if you using Webpack, you should use "bundler" version of Vue
Webpack.config.js
alias: {
vue: "vue/dist/vue.esm-bundler.js"
}
...you don't need to switch minified/dev bundle because Webpack will (when configured correctly) optimize Vue code same way as your own code..
Also, pay attention to this sentence in the docs: Leaves prod/dev branches with process.env.NODE_ENV guards (must be replaced by bundler)
NODE_ENV is conventionally used to define the environment type and is used by Vue to decide what code to include...
Note
I don't really understand why are You using your own Webpack config for project created with Vue CLI when whole point of Vue CLI is to manage webpack config for you and offers plenty of options to customize it...doesn't make any sense
If you are using Vite, add the alias 'vue': 'vue/dist/vue.esm-bundler' to vite.config.js
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'vue': 'vue/dist/vue.esm-bundler',
},
}
})
you just have to replace on Webpack.config.js
alias: {
vue: devMode ? "vue/dist/vue.runtime.esm-browser.js" : "vue/dist/vue.runtime.esm-browser.prod.js"
}
With
vue: "vue/dist/vue.esm-bundler.js"
This works for me.

vue.js - dynamic imports results in error: Support for the experimental syntax 'dynamicImport' isn't currently enabled

I'm learning Vue.js with Webpack for the first time today and trying to get a router working with lazy/dynamic imports.
I want to use lazy/dynamic imports because I am rebuilding my content management system which has many, many pages that may or may not be used during the user's session, so loading the modules they need dynamically, when they need them, makes more sense with regards to my application.
My very basic router currently looks like this:
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
function loadView(view) {
return () => import(/* webpackChunkName: "view-[request]" */ `#/views/${view}.vue`);
}
export default new Router({
routes: [
{
path: "/",
name: "dashboard",
component: loadView("Dashboard")
},
{
path: "/login",
name: "login",
component: loadView("Login")
}
]
});
However, I run into the following compilation error:
ERROR in ./src/router/index.js Module build failed (from
./node_modules/babel-loader/lib/index.js): SyntaxError:
...../src/router/index.js: Support for the experimental syntax
'dynamicImport' isn't currently enabled
With the additional note:
Add #babel/plugin-syntax-dynamic-import to the
'plugins' section of your Babel config to enable parsing.
And shows me which line is the problem, which is quite obvious anyway:
return () => import(/*..........
^
I recognise this error from when I was playing with Webpack on its own a few months ago, so I knew I had to install the dynamic import plugin to make this work.
This is what I installed:
npm install babel-plugin-syntax-dynamic-import
And I made this plugin available in my babel.rc configuration file and ran npm run dev to recompile everything:
{
"presets": [
[
"#babel/env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
],
"plugins": ["#babel/plugin-syntax-dynamic-import"]
}
But I'm still getting the error and I still can't use dynamic importing features! Am I doing something wrong? Has anybody else had trouble with getting dynamic imports to work?
My webpack.config file:
'use strict';
const webpack = require('webpack');
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
mode: 'development',
entry: [
'./src/app.js'
],
devServer: {
hot: true,
watchOptions: {
poll: true
}
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
outputStyle: 'compressed',
data: `
#import "./src/assets/scss/_variables.scss";
#import "./src/assets/scss/_mixins.scss";
`
}
}
]
}
]
},
resolve: {
alias: {
"#": path.resolve(__dirname, './src'),
"Components": path.resolve(__dirname, './src/components/')
}
},
optimization: {
minimizer: [new UglifyJsPlugin()],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
}
My package.json file:
{
"name": "manage_v2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config build/webpack.config.dev.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.5.22",
"vue-router": "^3.0.2",
"vuex": "^3.1.0"
},
"devDependencies": {
"#babel/core": "^7.2.2",
"#babel/plugin-syntax-dynamic-import": "^7.2.0",
"#babel/preset-env": "^7.3.1",
"babel-loader": "^8.0.5",
"babel-plugin-dynamic-import-webpack": "^1.1.0",
"css-loader": "^2.1.0",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.11.0",
"sass-loader": "^7.1.0",
"uglifyjs-webpack-plugin": "^2.1.1",
"vue-loader": "^15.6.2",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.5.22",
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
}
}
I fixed the problem myself after many hours of frustration. I still don't know why the method that's used in the Babel, Webpack and Vue documentation doesn't work but I did get this working:
I first removed the plugin declaration from babel.rc file and then added an option to the babel loader in webpack.config file:
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
plugins: [
"#babel/plugin-syntax-dynamic-import"
]
}
}
}
I hope this helps others.
You have the wrong plugin assignment:
"plugins": ["#babel/plugin-syntax-dynamic-import"]
Would be the correct format for that plugin.

Webpack build fails for IE (11)

Trying to setup webpack to build my .js and .vue files.
Seems to no transpile _name() {} style function to regular JavaScript.
Thought it should do that out of the box, straight into commons js unless otherwise specified.
No idea why, it's crashing only in IE with some generic JS syntax error about semi colon out of place.
I guess some little flag, setting, something, somewhere.
Looking for some suggestions here.
ex vue
<script>
export default {
computed: {
_name() {
return 'blah';
}
}
};
</script>
package.json
"#babel/core": "7.0.0-beta.42",
"#babel/plugin-proposal-object-rest-spread": "7.0.0-beta.42",
"#babel/preset-env": "7.0.0-beta.42",
"babel-loader": "8.0.0-beta.2",
"vue-loader": "9.5.1",
"vue-style-loader": "1.0.0",
"sass-loader": "3.2.3",
"node-sass": "^4.1.1",
"css-loader": "0.25.0",
"style-loader": "0.13.1",
"vue-html-loader": "1.2.3",
"file-loader": "^0.8.4",
"webpack": "3.4.1",
"webpack-dev-server": "1.16.1",
"webpack-stream": "3.2.0",
"copy-webpack-plugin": "3.0.1",
"replace-webpack-plugin": "0.1.2",
"uglifyjs-webpack-plugin": "1.2.7"
config
entry: [__dirname + '/../src/bootstrap.js'],
output: {
path: __dirname + '/../public',
filename: 'app.min.js',
chunkFilename: "[name].[chunkhash:4].js",
publicPath: '/',
},
plugins: [
new CopyWebpackPlugin(
(function () {
var copy = [{
to: '',
from: __dirname + '/../src/core/assets'
}, {
to: '',
from: __dirname + '/../src/app/assets'
}];
if (data.copy) {
copy.concat(data.copy);
}
return copy;
})()
),
new ReplacePlugin({
skip: false,
entry: 'src/index.html',
output: '/public/index.html',
hash: '[hash]',
data: {
scripts: data.scripts
}
})
],
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}, {
test: /\.vue$/,
// exclude: /node_modules/,
loader: 'vue-loader',
options: {
loaders: {
js: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
},
sass: 'sass-loader'
}
}
}]
}
build looks like this
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony default export */ __webpack_exports__["default"] = ({
props: ['name', 'timeout', 'max'],
computed: {
_name() {
return this.name || 'global';
},
...
EDIT:
In the end it was just a matter of adding a few specific transform plugins in the .babelrc file at the root. Probably better way to do this via config. Took a while to match the transforms to each error, but the following set worked for me.
{
"plugins": [
"#babel/plugin-transform-spread",
"#babel/plugin-transform-destructuring",
"#babel/plugin-transform-block-scoping",
"#babel/plugin-transform-arrow-functions",
"#babel/plugin-transform-template-literals",
"#babel/plugin-transform-computed-properties",
"#babel/plugin-transform-shorthand-properties"
]
}
Note: each plugin needs to be installed as a dependency also.
Since it is a custom webpack configuration and it is not clear there is any additional configuration to babel, try to add #babel/plugin-transform-shorthand-properties plugin to babel-loader options for .vue files.
loaders: {
js: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: ['#babel/plugin-transform-shorthand-properties']
}
},
sass: 'sass-loader'
}
from the output it is confirmed that shorthand property is not getting transpiled
you are using the shorthand property of ES6 which is not supported on iE you need to configure the babel config so that it transpiled this into older version ( for more info about browser support visit this link)
to make your babel config compatible for ie use something like this in babelrc
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "ie >= 11"]
},
"useBuiltIns": true
}],
]
}
if that dosen't work try changing you devtool config in webpack (for example eval to something else ) and check this thread of github

Webpack - trying to route/compile my CSS and JS files into CSS and JS specific sub-folders within dist/ folder

I'm pretty new to webpack in general. Just a couple of days in really. For the most part I feel like I've made some pretty good progress on at setting up and integrating webpack into my local development environment. Quite a few bugs and command line errors along the way but I was able to work through most of them up to now. Shout out to Maxmillian Schwärzmuller's short YT series on Webpack to get me started.
Now I'm trying to re-purpose/expand on some of the concepts I've picked up however I'm not yet having any success getting my npm run build (this would be the production command) to compile my CSS files into CSS and JS subfolders (off the root of the site) respectively.
I felt like I was making more progress with my attempts to use the file-loader package within my JS and CSS specific test blocks but now I'm wondering if I'm not either misunderstanding the purpose of the file-loader package or simply not configuring it correctly.
Here are the contents of my webpack.config.js file:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const extractPlugin = new ExtractTextPlugin({
filename: 'styles.css'
});
module.exports = {
entry: './src/js/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
// attempted to replicate the same use statement as seen below for the jpg/png test
// and only app.js (not app.bundle.js) was dropped into dist/js/
// {
// loader: 'file-loader',
// options: {
// name: '[name].[ext]',
// outputPath: 'js/',
// publicPath: 'js/'
// }
// }
]
},
{
test: /\.scss$/,
use: extractPlugin.extract({
use: ['css-loader', 'sass-loader']
})
// attempted to modify/replicate the above use statement
// use: [
// {
// loader: 'css-loader',
// loader: 'sass-loader'
// loader: 'file-loader',
// options: {
// name: '[name].[ext]',
// outputPath: 'css/',
// publicPath: 'css/'
// }
// }
// ]
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader'
}
]
},
{
test: /\.(jpg|png)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
publicPath: 'images/'
}
}
]
},
{
test: /\.html$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
],
exclude: path.resolve(__dirname, 'src/index.html')
}
]
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
extractPlugin,
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'])
]
};
Here is the package.json file:
{
"name": "nodomaintoseehere.com",
"version": "1.0.0",
"description": "Site repository v1.0",
"private": true,
"main": "./src/app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server",
"build": "webpack -p"
},
"author": "Jeremy Wilson",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"clean-webpack-plugin": "^0.1.19",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.11",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"jquery": "^3.3.1",
"node-sass": "^4.9.3",
"sass-loader": "^7.1.0",
"webpack": "^3.12.0",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^2.11.2"
},
"dependencies": {
"bootstrap": "^4.1.3",
"lodash": "^4.17.10"
}
}
And finally the app.js file
import '../css/styles.scss';
import 'babel-polyfill';
import $ from 'jquery';
Thanks in advance.
Putting assets into different subfolders does not make any differece in performance, nor in use since those are going to sit on your static server everytime until it is redeployed.
There is no way to output js files to js folder since there is no option on babel loader and file-loader does not handle js, they just emit the file, in your case it would endup with duplicated files at the end. File-loader is not ideal to work with javascript files.
For css, you can configure extract-text-plugin.
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const extractPlugin = new ExtractTextPlugin({
filename: 'css/styles.css' // <---
});
module.exports = {
entry: './src/js/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
]
},
{
test: /\.scss$/,
use: extractPlugin.extract({
use: ['css-loader', 'sass-loader']
})
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader'
}
]
},
{
test: /\.(jpg|png)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
publicPath: 'images/'
}
}
]
},
{
test: /\.html$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
],
exclude: path.resolve(__dirname, 'src/index.html')
}
]
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
extractPlugin,
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'])
]
};