How would I import a module within an npm package subfolder with webpack? - npm

Lets say theres a package in node_modules called foo and I want to import a module within a library such as foo/module via webpack & babel...
import Foo from 'foo'; works
import SomeOtherModule from 'foo/module'; fails with the following:
Module not found: Error: Cannot resolve module 'foo/module' in
/Users/x/Desktop/someproject/js
Which makes make it seem like webpack is looking for the file in the wrong place instead of node_modules
My webpack.config looks like this:
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: ['babel-polyfill','./js/script.js'],
output: {
path: __dirname,
filename: './build/script.js'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
query: {
cacheDirectory: true,
presets: ['es2015']
}
}
],
},
plugins: [
new webpack.NoErrorsPlugin()
],
stats: {
colors: true
},
devtool: 'source-map'
};

It should work with import 'foo/module';. It will resolve file ./node_modules/foo/module.js or ./node_modules/foo/module/index.js and not something like ./node_modules/foo/node_modules/module/index.js if it expected (in that case you better to install module via npm).

You can define a custom path using the module attribute in your package.json. Example:
{
...
"module": "dist/mylib.min.js",
...
}
See What is the "module" package.json field for?

Related

ESM library generated with rollup-plugin-postcss throws Cannot find module '../node_modules/style-inject/dist/style-inject.es.js'

We are maintaining an internal library which is exporting ESM modules using Rollup. We have just recently switched to using CSS modules, which we have set with rollup-plugin-postcss. We want to inject these styles into the head rather than have an external file.
Our built bundle generates the ESM file with:
import styleInject from '../node_modules/style-inject/dist/style-inject.es.js';
Our consuming library then fails with
Uncaught Error: Cannot find module '../node_modules/style-inject/dist/style-inject.es.js'
I would expect the ESM export to import styleInject from 'style-inject' and style-inject to be included in the package-lock.json as a dependency. What is the correct way of using CSS Modules and injecting into the head for the consumer of a library?
rollup.config.js
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import babel from '#rollup/plugin-babel';
import json from '#rollup/plugin-json';
import postcss from 'rollup-plugin-postcss';
import pkg from './package.json';
import fg from 'fast-glob';
import path from 'path';
export default [
{
input: 'src/index.js',
external: external(),
output: [
{
name: '#my/packageName',
file: pkg.module,
format: 'es',
sourcemap: true,
},
],
plugins: [
{
name: 'watch-external',
async buildStart() {
const files = await fg(['src/index.d.ts', 'playground/**/*']);
for (let file of files) {
this.addWatchFile(path.resolve(file));
}
},
},
json(),
postcss({
modules: true,
}),
babel({
exclude: /node_modules/,
babelHelpers: 'runtime',
babelrc: false,
presets: [
[
'#babel/preset-env',
{
modules: false,
useBuiltIns: 'entry',
corejs: 3,
targets: {
ie: 11,
},
},
],
'#babel/preset-react',
],
plugins: [
'#babel/plugin-transform-runtime',
'#babel/plugin-proposal-class-properties',
'#babel/plugin-proposal-export-namespace-from',
],
}),
commonjs(),
],
},
];
function external() {
const { dependencies = {}, peerDependencies = {} } = pkg;
const externals = [
...Object.keys(dependencies),
...Object.keys(peerDependencies),
];
return id =>
// match 'lodash' and 'lodash/fp/isEqual' for example
externals.some(dep => id === dep || id.startsWith(`${dep}/`));
}
There is a bug in the library where the import is relative rather than being the module name.
There is an open pr, but the library looks like it is no longer being maintained. There are two recommended solutions in the comments:
String replace the built output
Add a custom rollup plugin to do this
This is not a bug at all. It works properly because stylesInject is just a function from 'styles-inject' package, so we can allow it to be bundled. But it is not bundled because most likely you have this setting in your rollup.config.js:
external: [
/node_modules/
]
So, replace it with a code bellow and it will work:
external: (id) => {
if (/style-inject/.test(id)) return false;
if (/node_modules/.test(id)) return true;
return false;
},
also, you can write a regexp for it instead
You need to do 2 things:
Add style-inject dependency in package.json
"dependencies": {
"style-inject": "^0.3.0"
},
Add following plugin to rollup.config.js
const plugins = [
...
{
/**
* - https://github.com/egoist/rollup-plugin-postcss/issues/381#issuecomment-880771065
* - https://lightrun.com/answers/egoist-rollup-plugin-postcss-esm-library-generated-with-rollup-plugin-postcss-throws-cannot-find-module-node_modulesstyle-in
*/
name: 'Custom Rollup Plugin`',
generateBundle: (options, bundle) => {
Object.entries(bundle).forEach((entry) => {
// early return if the file we're currently looking at doesn't need to be acted upon by this plugin
if (!entry[0].match(/.*(.scss.js)$/)) {
return;
}
// this line only runs for .scss.js files, which were generated by the postcss plugin.
// depending on the use-case, the relative path to style-inject might need to change
bundle[entry[0]].code = entry[1].code.replace(
/\.\.?\/[^\n"?:*<>|]+\/style-inject\/dist\/style-inject.es.js/g,
'style-inject',
);
});
},
}
];
References:
https://github.com/egoist/rollup-plugin-postcss/issues/381#issuecomment-880771065
https://lightrun.com/answers/egoist-rollup-plugin-postcss-esm-library-generated-with-rollup-plugin-postcss-throws-cannot-find-module-node_modulesstyle-in

Vite library mode entry points

My vite config looks like this.
I want to have two entry points, as you can see in build.lib.entry I have the entry for my library, in this case src/main.js, but I would also like to have one entry for my project because I am testing locally.
// vite.config.js
const path = require('path')
module.exports = {
build: {
lib: {
entry: path.resolve(__dirname, 'src/main.js'),
name: 'MyLib'
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['vue'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: 'Vue'
}
}
}
}
}
I tried adding the following code inside module.exports, but it didn'T work.
entry: path.resolve(__dirname, 'src/app.js'),
https://vitejs.dev/guide/build.html#library-mode
In the vite discussions on github it seems you do it with something like this.
https://github.com/vitejs/vite/discussions/1736#discussioncomment-413068
build: {
rollupOptions: {
input: {
'entry-point-a': path.resolve(__dirname, 'src/entry-point-a.tsx'),
'entry-point-b': path.resolve(__dirname, 'src/entry-point-b.tsx'),
},
}
},
It looks like vite does not support multiple entries. You can use two vite configuration files.
eg. package.json :
"scripts": {
"build": "vite build --config vite.config.js",
"build:lib": "vite build --config vite-lib.config.js",
},

Webpack Vue component with css <style> tags fails to build with Module parse failed: Unexpected token

Starting with a clean vue project, I was having issues building .vue components from PrimeVue.
These are ready made components and should really not fail to build.
Everytime I try to build, it fails to do so, and seems to fail with the line pointer at the start of the CSS styles.
ERROR in ./node_modules/primevue/components/slider/Slider.vue?vue&type=style&index=0&lang=css& (./node_modules/vue-loader/lib??vue-loader-options!./node_modules/primevue/components/slider/Slider.vue?vue&type=style&index=0&lang=css&) 340:0
Module parse failed: Unexpected token (340:0)
File was processed with these loaders:
* ./node_modules/vue-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
|
|
> .p-slider {
| position: relative;
| }
# ./node_modules/primevue/components/slider/Slider.vue?vue&type=style&index=0&lang=css& 1:0-119 1:135-138 1:140-256 1:140-256
# ./node_modules/primevue/components/slider/Slider.vue
# ./node_modules/primevue/slider.js
# ./myproject/components/Test.js
# ./myproject/components/App.js
# ./myproject/main.js
This is my webpack config file:
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
mode: 'development',
entry: 'main.js',
output: {
filename: 'main.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
]
};
What is causing this error, as I am importing the components correctly as stated by the PrimeVue documentation.
Setting a rule in the webpack config to send the .vue files for processing to vue-loader is not enough.
You need to specify how to handle .css files too, and this then gets applied to tags in a .vue file as well. Without this rule, it will not know what to do with the <style> blocks, even if you dont plan on using the .css file part of this.
Update the rules section of the webpack.config.js with the following:
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
Also make sure that vue-style-loader and css-loader are installed in package.json.
More information can be found at the manual installation section of the vue-loader documentation, specifically the code example under 'A more complete example webpack config will look like this.'
I would highly recommend to cache the .vue files because they will slow down your build time in big projects.
// snippet from https://github.com/unic/darvin-webpack-boilerplate/blob/master/webpack/settings/javascript-vue/index.js
const {VueLoaderPlugin} = require('vue-loader');
const webpack = require('webpack');
const path = require('path');
const ROOT_PATH = process.cwd();
const CACHE_PATH = path.join(ROOT_PATH, 'tmp/cache');
const VUE_VERSION = require('vue/package.json').version;
const VUE_LOADER_VERSION = require('vue-loader/package.json').version;
const dev = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
cacheDirectory: path.join(CACHE_PATH, 'vue-loader'),
cacheIdentifier: [
process.env.NODE_ENV || 'development',
webpack.version,
VUE_VERSION,
VUE_LOADER_VERSION,
].join('|'),
},
}
]
},
plugins: [
new VueLoaderPlugin(),
],
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['.js', '.vue', '.json'],
},
};

Webpack using wrong node_modules folder

I have a vue client project that uses a vue library project (the vue library project is also using some 3rd party packages like vue-material).
They are linked via the client project's Package.json like this "lib": "file:../lib" and I am importing components in the client project using import Comp from "lib/src/components/Comp";
The problem is that when I build the client project using Webpack, the files in my library use lib/node_modules/vue instead of node_modules/vue which causes double vue instancing.
Anyone has any idea why when I am using webpack build from the client folder, it looks for vue package in my library folder? and is there a way to get around that?
My webpack.config
"use strict";
const path = require("path");
const utils = require("./utils");
const config = require("../config");
const vueLoaderConfig = require("./vue-loader.conf");
function resolve(dir) {
return path.join(__dirname, "..", dir);
}
module.exports = {
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"),
src: resolve("src"),
assets: resolve("src/assets"),
components: resolve("src/components"),
utilities: resolve("src/utilities"),
directives: resolve("src/directives"),
plugins: resolve("src/plugins"),
data: resolve("src/data"),
"vuex-store": resolve("src/store"),
"lib": resolve("node_modules/lib")
}
},
module: {
rules: [
{
test: /\.(js|vue)$/,
loader: "eslint-loader",
enforce: "pre",
include: [resolve("src")],
options: {
formatter: require("eslint-friendly-formatter")
}
},
{
test: /\.vue$/,
loader: "vue-loader",
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: "babel-loader",
include: [resolve("src"), resolve("../lib/src")]
},
{
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: "base64-font-loader",
options: {
limit: 10000,
name: utils.assetsPath("fonts/[name].[hash:7].[ext]")
}
},
{
test: /\.ico$/,
loader: "file-loader?name=[name].[ext]"
}
]
}
};
My client's main entry
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
// Core Imports
import Vue from 'vue'
import App from './App'
// Plugins
import { ComponentsPlugin } from "lib/src/components";
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
As I ran out of solutions to this problem I decided to debug webpack's compiler.
It seems webpack uses a library called 'enhanced-resolve' (We actually send parameters to that library in our webpack's resolve:{} section). And using the resolve.alias property we can redirect any require to any folder we want.
So to redirect any require("vue") to my own vue.esm.js, all I need is to add an entry like this (I originally had that line but it was pointing to a relative path rather than an absolute path):
resolve: {
alias: {
vue$: resolve("node_modules/vue/dist/vue.esm.js"),
}
}
Note the $ at the end of the library name, It signals enhanced-resolve that the package only has 1 module so any require whos name is "vue" or a sub-directory of "vue" should be parsed using the alias.
Enhanced-Resolve - ResolverFactory.js
if(/\$$/.test(alias)) {
onlyModule = true;
....
}
Enhanced-Resolve - AliasPlugin.js
// InnerRequest is the path (vue/dist/vue.esm.js)
// name is the alias name without the $ (vue)
if(innerRequest === name || (!onlyModule && startsWith(innerRequest, name + "/"))) {
continue resolving....
}

webpack gives module.js error

I use windows and have installed webpack already globally. I ran the webpack command to build a bundle. When I run the command, I get the error module.js throw err;. Please see the image below.
The content of the webpack.config.js is
var webpack = require('webpack');
module.exports = {
context: __dirname,
entry: './app.js',
module: {
loaders: [
{ test: /\.js$/, loader: 'jsx-loader' }
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {NODE_ENV: JSON.stringify('production')}
})
]
};
In addition to having webpack install as a Global module, you need to add it to your local dev dependencies.
the following should resolve the above mentioned issue.
> npm i -D webpack