Integration of back-end webpack with Vue-CLI 3 - vue.js

I'm developing a full-stack application using Vue-CLI 3. The backend is written in TypeScript and so requires compilation etc. Currently I'm just using ts-node to run the backend app directly which works, but it would be much cleaner if I could also webpack the backend app to give a single server.js rather than multiple sprawling typescript files all over the place.
The problem is that the way I'd do this normally is have a single webpack.base.js and then include that in different webpack config files. I can't really do that because a lot of the webpack configuration is hidden away in vue-cli-service

Ended up pretty much having a base webpack config and extending client and server webpack configuration.
My app root webpack.config.js looks like this:
const { config: client } = require('./src/client/webpack.config');
const { config: server } = require('./src/server/webpack.config');
/**
* Webpack Build File
*
* This webpack configuration is used for only building the client and server
* bundles. It imports both of these from their respective directories, but
* allows for overrides if required.
*
* Other dev tools such as watching, hot module reloading etc. has been split
* out into other config files
*
* #param {object} env Webpack `env` object
*/
module.exports = ({ mode = 'development' } = {}) => ([
{
...client({ mode }, process.env),
},
{
...server({ mode }, process.env),
},
]);
My src/client/webpack.config.js looks like this:
const { resolve: _resolve, sep } = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TSConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackRootElementPlugin = require('html-webpack-root-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const { DefinePlugin } = require('webpack');
const paths = {
src: {
rootDir: _resolve(__dirname) + sep,
app: _resolve(__dirname, 'app') + sep,
},
dist: {
rootDir: _resolve(__dirname, '..', '..', 'dist'),
app: _resolve(__dirname, '..', '..', 'dist', 'public'),
},
};
/**
* Client webpack build configuration.
*
* This webpack config produces a bundle for the client-side application only.
*
* #param {object} webpackEnv Webpack env object (basically any/all options passed in via the CLI)
* #param {object} processEnv Process env object (environment variables from process.env)
*/
const config = ({ mode = 'none' }, { APP_NAME = '', BASE_URL = '/' } = {}) => ({
name: 'client',
target: 'web',
mode,
entry: {
app: paths.src.app + 'main.ts',
},
output: {
path: paths.dist.app,
},
optimization: {
runtimeChunk: 'single',
},
resolve: {
extensions: ['.ts', '.js', '.vue'],
plugins: [
new TSConfigPathsPlugin({
configFile: paths.src.rootDir + 'tsconfig.json',
}),
],
},
context: paths.src.rootDir,
module: {
rules: [
{
test: /\.tsx?$/,
include: paths.src.rootDir,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
happyPackMode: true,
},
},
],
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.s?[ac]ss$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
{
test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
loader: 'file-loader',
options: {
name: 'assets/[name].[hash].[ext]',
esModule: false,
},
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
favicon: paths.src.app + 'assets/logo.png',
title: APP_NAME,
}),
new HtmlWebpackRootElementPlugin('app'),
new VueLoaderPlugin(),
new DefinePlugin({
'process.env.BASE_URL': JSON.stringify(BASE_URL),
}),
],
});
module.exports = {
config,
paths,
};
and my src/server/webpack.config.js looks like this:
const { resolve: _resolve, sep } = require('path');
const TSConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const { IgnorePlugin } = require('webpack');
const paths = {
src: _resolve(__dirname) + sep,
dist: _resolve(__dirname, '..', '..', 'dist'),
};
/**
* NestJs uses a custom wrapper around require() that allows it to show a
* warning when some extra package needs to be installed. This causes problems
* with webpack, so we're blacklisting packages we're not using with the
* IgnorePlugin below.
*
* To de-blacklist a package, just remove it from this array.
*/
const nestBlacklist = [
'^cache-manager$',
'^#nestjs/microservices$',
// packages below are required from microservices
'^amqp-connection-manager$',
'^amqplib$',
'^grpc$',
'^mqtt$',
'^nats$',
'^redis$',
];
/**
* Server webpack build configuration.
*
* This webpack config produces a bundle for the server-side application only.
*
* #param {object} webpackEnv Webpack env object (basically any/all options passed in via the CLI)
* #param {object} processEnv Process env object (environment variables from process.env)
*/
const config = ({ mode = 'none' }) => ({
name: 'server',
mode,
target: 'node',
entry: paths.src + 'main.ts',
externals: [nodeExternals()],
output: {
path: paths.dist,
filename: 'server.js',
},
resolve: {
extensions: ['.ts', '.js'],
plugins: [
new TSConfigPathsPlugin({
configFile: './tsconfig.build.json',
}),
],
},
context: paths.src,
optimization: {
minimize: false,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.ts$/,
include: paths.src,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
happyPackMode: true,
},
},
],
},
],
},
plugins: [
new IgnorePlugin({
contextRegExp: /#nestjs/,
resourceRegExp: new RegExp(nestBlacklist.join('|')),
}),
],
node: {
__dirname: false,
},
});
module.exports = {
config,
paths,
};
This allows me to have distinct server and client side compilation configuration, but in such a way that's re-usable in other configurations. So, for webpack dev server - I have webpack.dev.config.js which contains the following:
const client = require('./src/client/webpack.config');
const server = require('./src/server/webpack.config');
const { HotModuleReplacementPlugin } = require('webpack');
const {
SERVER_PORT = 3000,
} = process.env;
/**
* Watch settings are the same between client and server, so we're keeping them
* here for consistency
*/
const watchConfig = {
watch: true,
watchOptions: {
ignored: /node_modules/,
},
};
/**
* Development Webpack Build File
*
* This webpack configuration extends `webpack.config.js` and builds on it to
* provide hot module replacement, watch moide and a dev server for the
* client-side code
*
* Other dev tools such as watching, hot module reloading etc. has been split
* out into other config files
*
* #param {object} env Webpack `env` object
*/
module.exports = ({ mode = 'development' } = {}) => ([
{
...client.config({ mode }, process.env),
...watchConfig,
devServer: {
contentBase: client.paths.dist.app,
historyApiFallback: {
rewrites: [
{ from: /./, to: '/index.html' },
],
},
port: 8000,
host: '0.0.0.0',
hot: true,
hotOnly: true,
proxy: {
'/api': `http://localhost:${SERVER_PORT}/`,
},
},
},
{
...server.config({ mode }, process.env),
...watchConfig,
plugins: [
...server.config({ mode }, process.env).plugins,
new HotModuleReplacementPlugin(),
],
},
]);
This config provides a setup that produces a server.js which then serves up a directory (in this case '/public'`) as it's static files dir.

Related

Module parse failed: Unexpected character '#' even with shebang loader

I am trying to use https://github.com/dipson88/treeselectjs in my polymer 3 application.
Though, when I try to import it in one of my js file like
import Treeselect from 'treeselectjs'
it throws the following error:
Module parse failed: Unexpected character '#' (1:2341) File was
processed with these loaders:
my webpack is :
const merge = require('webpack-merge');
const flowDefaults = require('./webpack.generated.js');
module.exports = merge(flowDefaults, {
mode: 'production',
module: {
rules: [
{
test: /\.svg$/,
use: {
loader: 'html-loader',
options: {
minimize: true
}
}
},
{
test: /\.mjs.js$/,
loader: 'string-replace-loader',
options: {
search: '#',
replace: '',
}
},
{
test: /\.js$/,
loader: ['shebang-loader','ify-loader']
}
]
},
stats: {
warnings: false
}
});
How can I resolve this

How to distinguish files which have the same name but in different sub-folders in sourcemap?

In my VUE project, there are several templates which have the same filename but located in different source sub-folders. I'm using webpack 3.12 to build it, and the devtool set to 'cheap-module-eval-source-map'.
After I run webpack-dev-server 2.11.1 to debug it, all template source files are put into the root folder 'webpack://' of the browser's sourcemap, so ONLY one of these files can exist, others are lost, I can't debug them.
Is there a way to make these files co-existing in the sourcemap?
module.exports = {
plugins: [
new VueLoaderPlugin()
],
context: path.resolve(__dirname, '../'),
entry: {
app: ['babel-polyfill', './src/main.js']
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production' ?
config.build.assetsPublicPath : config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'#': resolve('src'),
}
},
devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [{
from: /.*/,
to: path.posix.join(config.dev.assetsPublicPath, 'index.html')
}, ],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay ?
{
warnings: false,
errors: true
} :
false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
}
}
At last, I changed 'cheap-module-eval-source-map' to 'module-eval-source-map', the relative path of source files are kept.
I've read the official document of devtool, it says "it doesn't have column mappings, it only maps line numbers." if "cheap" mode used, I don't understand why the relative path is discarded.
eval-cheap-source-map - Similar to eval-source-map, each module is executed with eval(). It is "cheap" because it doesn't have column
mappings, it only maps line numbers. It ignores SourceMaps from
Loaders and only display transpiled code similar to the eval devtool.
eval-cheap-module-source-map - Similar to eval-cheap-source-map, however, in this case Source Maps from Loaders are processed for
better results. However Loader Source Maps are simplified to a single
mapping per line.
This worked for me when using vue-cli-service serve: https://github.com/vuejs/vue-cli/issues/2978#issuecomment-577364101
In vue.config.js:
let path = require('path')
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'development') {
// See available sourcemaps:
// https://webpack.js.org/configuration/devtool/#devtool
config.devtool = 'eval-source-map'
// console.log(`NOTICE: vue.config.js directive: ${config.devtool}`)
config.output.devtoolModuleFilenameTemplate = info => {
let resPath = path.normalize(info.resourcePath)
let isVue = resPath.match(/\.vue$/)
let isGenerated = info.allLoaders
let generated = `webpack-generated:///${resPath}?${info.hash}`
let vuesource = `vue-source:///${resPath}`
return isVue && isGenerated ? generated : vuesource
}
config.output.devtoolFallbackModuleFilenameTemplate =
'webpack:///[resource-path]?[hash]'
}
},
}

building vuejs plugin with webpack

ERROR in ./src/Loader.vue
Module parse failed: C:\test\vuePlugin\src\Loader.vue Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (1:0)
at Parser.pp$4.raise (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:2221:15)
at Parser.pp.unexpected (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:603:10)
at Parser.pp$3.parseExprAtom (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:1822:12)
at Parser.pp$3.parseExprSubscripts (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:1715:21)
at Parser.pp$3.parseMaybeUnary (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:1692:19)
at Parser.pp$3.parseExprOps (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:1637:21)
at Parser.pp$3.parseMaybeConditional (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:1620:21)
at Parser.pp$3.parseMaybeAssign (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:1597:21)
at Parser.pp$3.parseExpression (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:1573:21)
at Parser.pp$1.parseStatement (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:727:47)
at Parser.pp$1.parseTopLevel (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:638:25)
at Parser.parse (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:516:17)
at Object.parse (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\acorn\dist\acorn.js:3098:39)
at Parser.parse (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\lib\Parser.js:902:15)
at NormalModule. (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\lib\NormalModule.js:104:16)
at NormalModule.onModuleBuild (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
at nextLoader (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
at C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\webpack-core\lib\NormalModuleMixin.js:259:5
at Storage.finished (C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:38:16)
at C:\Users\shubham-sharma2\AppData\Roaming\npm\node_modules\webpack\node_modules\graceful-fs\graceful-fs.js:78:16
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:511:3)
# multi main
"webpack --config --display-error-details ./webpack.config.js"
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
var config = {
entry:path.resolve(__dirname,'src/plugin.js'),
output: {
path: path.resolve( __dirname,'/dist/')
},
module: {
rules: [ {
test: /\.js$/,
loader: 'babel-loader',
include: __dirname,
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader'
} ]
},resolve: {
extensions: ['.vue', '.js', '.jsx','']
},plugins:[ new webpack.optimize.UglifyJsPlugin( {
minimize : true,
sourceMap : false,
mangle: true,
compress: {
warnings: false
}
} )]
};
config.node={
fs:'empty'
};
module.exports = [
merge(config, {
entry: path.resolve(__dirname,'src/plugin.js'),
output: {
filename: 'loader.min.js',
libraryTarget: 'window',
library: 'Loader',
}
}),
merge(config, {
entry: path.resolve(__dirname , 'src/Loader.vue'),
output: {
filename: 'loader.js',
libraryTarget: 'umd',
library: 'Loader',
umdNamedDefine: true
}
})
// Config 1: For browser environment
// merge(commonConfig, {
// }),
// Config 2: For Node-based development environments
// merge(commonConfig, {
// })
];
Add
const VueLoaderPlugin = require('vue-loader/lib/plugin')
to the top of your webpack config, and
new VueLoaderPlugin()
in your plugin section.
And for your style blocks, add:
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
You might also try:
extensions: ['', '.vue', '.js', '.jsx']
I have seen the order become an issue.

karma-coverage includes node_modules

I have a karma config which for my unit test and code-cov. My project dir looks like below
RootFOlder
-karma.config.js
-webpack.test.config.js
-src/
--test.ts
--components
---ButonComponent
----Buttoncomponent.spec.ts
And my karma.config is below
// Karma configuration
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'src/test.ts',
'src/components/**/*.component.ts',
],
// list of files / patterns to exclude
exclude: [
'node_modules',
'./src/tsconfig.spec.json'
],
plugins: [
require('karma-jasmine'),
require("karma-coverage"),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-webpack'),
require('karma-sourcemap-loader'),
require('ts-loader')
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'src/components/**/*.ts': ['coverage'],
'src/components/**/*.component.ts': ['webpack', 'sourcemap'],
'src/test.ts': ['webpack', 'sourcemap'],
},
webpack: require('./webpack.test.config'),
coverageReporter: {
reporters: [
{ type: 'text', subdir: 'text' },
{ type: 'html', subdir: 'report-html' },
]
},
...
...
});
}
And my webpack.
module.exports = {
devtool: 'inline-source-map',
mode: 'development',
target: 'node',
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: ['ts-loader', 'angular-router-loader', 'angular2-template-loader']
}
....
....
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'test')
}),
// Removes warnings regarding Critical dependency
new webpack.ContextReplacementPlugin(
/\#angular(\\|\/)core(\\|\/)f?esm5/, path.join(__dirname, './src')
)
],
node: {
console: false,
global: true,
process: true,
Buffer: false,
setImmediate: false
}
}
The problem is after i run my test, my coverage covers the bundled node_modules. The file coverage endup being hude running in MBytes and my coverage low. Pls how do i exclude the node_modules from my coverage? Any help is appreciated .
By default you shouldn't even need to mention node_modules in the exclude path. Try removing it and see if the coverage is corrected?
If not,
try adding this to the preprocessors:
'!node_modules/**/*.*': ['coverage']

Webpack and Express - Critical Dependencies Warning

I have the following webpack.config.ts:
var webpack = require( 'webpack' );
var path = require( 'path' );
module.exports = {
entry: [
'./api/bin/www.ts'
],
output: {
path: path.resolve( __dirname, './dist/api' ),
filename: 'index.js'
},
module: {
loaders: [
{ test: /\.ts$/, loader: 'awesome-typescript-loader' },
{ test: /\.json$/, loader: 'json-loader' }
]
},
resolve: {
extensions: [ '', '.js', '.ts' ]
},
target: 'node',
node: {
console: true,
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
When I run webpack I get a warning about a dependency:
WARNING in ./~/express/lib/view.js
Critical dependencies:
78:29-56 the request of a dependency is an expression
# ./~/express/lib/view.js 78:29-56
The express server I start with this is no more than a Hello World example and functions as should but I am concerned about this warning.
My googlefu hasn't revealed any passable solutions. I have seen one particular instance of this problem but the solutions were to bypass the warning by not showing it.
Use webpack-node-externals.
const nodeExternals = require('webpack-node-externals');
{
target: 'node',
externals: [nodeExternals()],
}
https://www.npmjs.com/package/webpack-node-externals
For those that only need to remove the express due to the view lib as mentioned here you can also explicitly target express in externals from your webpack config.
externals: [{ 'express': { commonjs: 'express' } }]
My warning only got fixed with:
module.exports =
{
target: 'node',
externals: {
"express": "require('express')"
}
}
Instead of excluding all of the npm dependencies to be bundled with nodeExternals you can also exclude only express by natively requiring it by replacing
import express from 'express';
// Or
const express = require('express');
To
const express = __non_webpack_require__('express');
That will suppress the warning caused by express