i'm developing an application with react.js. My application is growing up. So i had little trouble with imports. For example i have a component named foo i'm using it in many places.
import foo from '../../components/foo';
import foo from '../components/foo';
import foo from '../../../components/foo';
As you can see its dirty, not good. So i searched to fix it and i found a solution with webpack. Also i read that title (Configure Webpack’s modules resolution to avoid nested imports) in this article
I added this code into my webpack.config.js file
modules: [
'node_modules',
path.resolve(__dirname, 'src')
]
So my resolve object looks like this
export default {
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'src')
],
extensions: ['*', '.js', '.jsx', '.json']
},
...
After that i am able to use import my foo component in anywhere like this.
import foo from 'components/foo';
Everything is okay so far. But problem shows up in test files.
When i try to test foo component it says
Cannot find module 'components/foo' from 'foo.js'
Example test file.
foo.spec.js
import React from 'react';
import foo from 'components/foo';
describe('(Component) foo', () => {
it('should render foo', () => {
expect(true).toBe(true);
});
});
Here is the first problem. I can not import foo like this.
Note: My test file is not in src folder it is in the test folder.
So i changed the path like this then it worked.
import foo from '../../../src/components/foo';
Tes passed everything is looks fine. But we still have the path problem in test files.
Lets try to import another component in foo component.
foo.js
import bar from 'components/admin/bar';
Here is the second problem. Test file FAILED error message is
Cannot find module 'components/admin/bar' from 'foo.js'
I moved my test file in to my foo.js file. But didn't worked.
Here is my whole webpack.config.js
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import autoprefixer from 'autoprefixer';
import path from 'path';
export default {
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'src')
],
extensions: ['*', '.js', '.jsx', '.json']
},
devtool: 'inline-source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool
entry: [
// must be first entry to properly set public path
'./src/webpack-public-path',
'webpack-hot-middleware/client?reload=true',
path.resolve(__dirname, 'src/index.js') // Defining path seems necessary for this to work consistently on Windows machines.
],
target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test
output: {
path: path.resolve(__dirname, 'dist'), // Note: Physical files are only output by the production build task `npm run build`.
publicPath: '/',
filename: 'bundle.js'
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'), // Tells React to build in either dev or prod modes. https://facebook.github.io/react/downloads.html (See bottom)
__DEV__: true,
//'API_URL': API_URL.dev
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new HtmlWebpackPlugin({ // Create HTML file that includes references to bundled CSS and JS.
template: 'src/index.ejs',
minify: {
removeComments: true,
collapseWhitespace: true
},
inject: true
}),
new webpack.LoaderOptionsPlugin({
minimize: false,
debug: true,
noInfo: true, // set to false to see a list of every file being bundled.
options: {
sassLoader: {
includePaths: [path.resolve(__dirname, 'src', 'scss')]
},
context: '/',
postcss: () => [autoprefixer],
}
})
],
module: {
rules: [
{test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel-loader']},
{test: /\.eot(\?v=\d+.\d+.\d+)?$/, loader: 'file-loader'},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&mimetype=application/font-woff'
},
{test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'},
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'},
{test: /\.(jpe?g|png|gif)$/i, loader: 'file-loader?name=[name].[ext]'},
{test: /\.ico$/, loader: 'file-loader?name=[name].[ext]'},
{
test: /(\.css|\.scss|\.sass)$/,
loaders: ['style-loader', 'css-loader?sourceMap', 'postcss-loader', 'sass-loader?sourceMap']
}
]
}
};
How can i solve?
Thanks for your help.
Webpack is not used during test execution. Since you are using babel the babel-plugin-module-resolver https://github.com/tleunen/babel-plugin-module-resolver should solve the issue:
in your .babelrc file
{
"plugins": [
["module-resolver", {
"root": ["./src"]
}]
]
}
A more cleaner approach would be to create an alias in your .babelrc file and then import from that alias, for instance:
{
"plugins": [
["module-resolver", {
"alias": {
"#app": "./src"
}
}]
]
}
And in your file:
import foo from '#app/components/foo'
That way you have no naming conflicts and your paths are nice and short.
Problem solved after i changed this jest object in package.json file.
"jest": {
"moduleDirectories": [
"node_modules",
"src"
]
...
Related
Firstly, I scaffolded a vuejs project as a test container from vue-cli.
Then I create a npm package "vue-npm-example" from a Vuejs component in local environment and imported in the above testing project.
In the package,
I ran npm link and in the project I ran npm link vue-npm-example,
Example.vue
<template>
<div id="vue-npm-example">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'vue-npm-example',
data() {
return {
msg: "Welcome to Your Vue.js App"
}
},
mounted() {
console.log('this is in compoennt file')
}
};
</script>
<style lang="scss">
</style>
main.js
import Example from './components/Example.vue'
export function install(Vue, options) {
options = {
installComponents: true
}
if (options.installComponents) {
Vue.component('vuejs-example', Example)
}
}
export default Example
webpack.config.js
let path = require('path')
let webpack = require('webpack')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'index.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.common.js',
'#': resolve('src')
},
extensions: ['*', '.js', '.vue', '.json']
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
},
devtool: '#eval-source-map',
node: {
fs: 'empty'
},
watch: true
}
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
})
])
}
Then in the testing project
I do
import Vue from 'vue'
import Example from 'vue-npm-example'
Vue.component('example', Example)
and use it like
<example></example>
I got error:
[Vue warn]: Failed to mount component: template or render function not defined.
I set the vue alias in the webpack config files for both package and project. The package got pulled in correctly because when I do console.log() in the package's main.js, it logs in the testing project. But no matter what I tried, the component in the package still won't work in the testing project.
Any suggestions?
npm link create an symlink, but when import the local npm package, I need to specify the full address of the package. In my case, I have to do import customComponent from './node_modules/custom-component/dist/index.js'
import customComponent from 'custom-component`.
I'd use npm pack instead (some downsides of using npm link to test your npm package locally: https://blog.vcarl.com/testing-packages-before-publishing/)
In the package
Run npm pack
In the project
Run npm install (path-to-package)/package-name-0.0.0.tgz
Then import/install the package in your main.js:
import MyPackage from 'package-name'
// This runs your 'install' method which registers your component globally with Vue.component(...)
Vue.use(MyPackage);
Some useful links
Packaging Vue components for npm: https://v2.vuejs.org/v2/cookbook/packaging-sfc-for-npm.html
Vue npm walkthrough: https://www.telerik.com/blogs/vuejs-how-to-build-your-first-package-publish-it-on-npm
Global component registration: https://v2.vuejs.org/v2/guide/components-registration.html#Global-Registration
I want to build a react component library as a node module to then import it into different projects. But if I try to import a component it just returns an empty object.
button.jsx:
import React, {Component} from 'react'
export class Button extends Component {
render() {
return <button className='btn'>Hello Button comp</button>
}
}
export default Button
index.js
var Button = require('./button/button').default;
module.exports = {
Button: Button
}
webpack.config.js
const Path = require('path');
module.exports = {
resolve: {
extensions: ['.js', '.jsx']
},
entry: {
app: './src/components/index.js'
},
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx$/,
loader: 'babel-loader',
query: {
presets: [
'es2015',
'react'
]
},
exclude: /node_modules/,
include: [
Path.resolve(__dirname, 'src')
]
},
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: [
'es2015',
'react'
]
},
exclude: /node_modules/,
include: [
Path.resolve(__dirname, 'src')
]
}
]
}
}
Main property in package.json is bundle.js
I figured out that when I import Button in a project it is just an empty object. It seems to me as if webpack doesn't bundle the index file properly. Any ideas what could be wrong here?
A webpack bundle does not expose your exports by default, as it assumes that you're building an app and not a library (which is the far more common use of webpack). You can create a library by configuring output.library and output.libraryTarget.
output: {
path: __dirname,
filename: 'bundle.js',
library: 'yourLibName',
libraryTarget: 'commonjs2'
},
output.libraryTarget is the format of the module, which would also allow you to expose the library as a global variable. commonjs2 is the module format that Node uses. See What is commonjs2? for the difference between commonjs and commonjs2.
Since you're using React, you'll expect that the consumer of the library will have React present as a dependency and therefore you don't want to include it in your bundle. To do that you can define it as an External. This is shown in Authoring Libraries, which walks you through a small example.
I am building a component library and I am using Webpack to bundle it. Some components only rely on html templates, css and JavaScript that I've written, but some components require external libraries.
What I'd like to achieve is a vendor.js that is optional to include if the component you want to use needs it.
For instance, If a user only needs a component without vendor dependencies, it would suffice that they use main.bundle.js which only contains my own code.
In my index.js, I have the following imports:
import { Header } from './components/header/header.component';
import { Logotype } from './components/logotype/logotype.component';
import { Card } from './components/card/card.component';
import { NavigationCard } from './components/navigation-card/navigation-card.component';
import { AbstractComponent } from './components/base/component.abstract';
import { Configuration } from './system.config';
import 'bootstrap-table';
import './scss/base.scss';
All of these imports are my own, expect for bootstrap-table.
I have configured Webpack like this:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractScss = new ExtractTextPlugin({
filename: "[name].bundle.css"
});
module.exports = {
entry: {
main: './src/index.ts'
},
output: {
path: path.resolve(__dirname, 'dist/release'),
filename: "[name].bundle.js",
chunkFilename: "[name].bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', // Specify the common bundle's name.
minChunks: function (module) {
// Here I would like to tell Webpack to give
// each bundle the ability to run independently
return module.context && module.context.indexOf('node_modules') >= 0;
}
}),
extractScss
],
devtool: "source-map",
resolve: {
// Add `.ts` as a resolvable extension.
extensions: ['.webpack.js', '.web.js', '.ts', '.js', '.ejs']
},
module: {
rules: [
// All files with a '.ts' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.ts?$/, exclude: /node_modules/, loader: "awesome-typescript-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
// Allows for templates in separate ejs files
{test: /\.ejs$/, loader: 'ejs-compiled-loader'},
{
test: /\.scss$/,
use: extractScss.extract({
use: [{
loader: 'css-loader', options: {
sourceMap: true
}
}, {
loader: 'sass-loader', options: {
soureMap: true
}
}]
})}
]
}
}
This results in two .js files and one .css. However, webpacks common module loading functionality resides in vendor.js, and that renders my main unusable if I don't include vendor first, and it isn't always needed.
To sum it up, if a user only needs the footer (no external dependencies), this would suffice:
<script src="main.bundle.js"></script>
If the user wants to use the table, which has an external dependency, they would need to include both:
<script src="vendor.js"></script>
<script src="main.bundle.js"></script>
Right now, including only main.bundle.js gives me this error:
Uncaught ReferenceError: webpackJsonp is not defined.
I am aware that I can extract all common functionality by adding this after my vendor chunk is created in the Webpack config:
new webpack.optimize.CommonsChunkPlugin({
name: 'common'
})
But this approach still requires the user to include two .js files.
How can I go about achieving this? It seems that it only differs 2 kb when I don't extract the common modules like I do above, and that is fine with me.
Turns out this is very easy to do if you can stand some manual work and actually understand what Webpack does (which I didn't). I solved it like this:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractScss = new ExtractTextPlugin({
filename: "[name].bundle.css"
});
module.exports = {
entry: {
main: './src/index.ts',
vendor: './src/vendor/vendor.ts'
},
output: {
path: path.resolve(__dirname, 'dist/release'),
filename: "[name].bundle.js",
chunkFilename: "[name].bundle.js"
},
plugins: [
extractScss
],
devtool: "source-map",
resolve: {
// Add `.ts` as a resolvable extension.
extensions: ['.webpack.js', '.web.js', '.ts', '.js', '.ejs']
},
module: {
rules: [
// All files with a '.ts' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.ts?$/, exclude: /node_modules/, loader: "awesome-typescript-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
// Allows for templates in separate ejs files
{test: /\.ejs$/, loader: 'ejs-compiled-loader'},
{
test: /\.scss$/,
use: extractScss.extract({
use: [{
loader: 'css-loader', options: {
sourceMap: true
}
}, {
loader: 'sass-loader', options: {
soureMap: true
}
}]
})}
]
}
}
In vendor.ts, I then simply import any vendor dependencies I have:
import 'jquery';
import 'bootstrap-table';
This results in two different files, both have Webpacks bootstrapping logic.
Hope this helps someone.
I started a new Vuetify / Webpack project, and tried to implement vue-router after setting up a project via vue init vuetify/webpack.
I set up the router based on the instructions from this tutorial. After some fiddling, I got it working by changing the way I imported Vue components.
In my router/index.js file:
// works for me
import Main from '../components/Main.vue'
// does NOT work; from the tutorial
import Main from '#/components/Main'
My question is, why do I have to import my Main.vue file relatively and include the .vue extension on the import?
My project structure:
-node_modules/
-public/
-src/
|-components/
||-Main.vue
|-router/
||-index.js
|-App.vue
|main.js
-index.html
-package.json
-webpack.config.js
My webpack.config.js file:
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'
},
resolve: {
alias: {
'public': path.resolve(__dirname, './public')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
objectAssign: 'Object.assign'
}
},
{
test: /\.styl$/,
loader: ['style-loader', 'css-loader', 'stylus-loader']
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
You are attempting to load a file from an alias directory named #. But in your webpack config file, you haven't defined that alias.
Also, you are required to specify the .vue extension because you haven't added it to the resolvable extensions in the resolve property in your config object.
In your webpack.config.js file, add a list of extensions to resolve and an alias called # which maps to your src directory:
resolve: {
extensions: ['', '.js', '.vue'],
alias: {
'#': path.resolve(__dirname, './src'),
...
}
...
}
Edit: #evetterdrake informed me that when using vue-cli to set up a project with Vuetify, the resolve config property is positioned after the module property, which is different than when setting up a normal Webpack project.
Be sure to add these config options to the existing resolve property or it will be overwritten and ignored.
I'm creating a React Node.js app and I'm trying to generate a Webpack bundle containing the React source code I loaded from NPM.
However, it seems that the React code from NPM cannot be used directly in the client. It triggers this error:
Uncaught ReferenceError: development is not defined
The code that triggers the exception is from the React code:
Is there anything I can do to make that work?
EDIT
This is my webpack.config.js:
import _ from 'lodash';
import webpack from 'webpack';
import yargs from 'yargs';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
export default {
entry: {
bundle: './src/client.js'
},
output: {
filename: '[name].js',
path: './dist/assets',
publicPath: '/assets/'
},
externals: undefined,
resolve: {
extensions: ['', '.js', '.json']
},
module: {
loaders: [
{test: /\.js/, loader: 'babel?optional=es7.objectRestSpread!client', exclude: /node_modules/ },
{test: /\.css/, loader: ExtractTextPlugin.extract("style-loader", "css-loader")},
{test: /\.less$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")},
{test: /\.json$/, loader: 'json'},
{test: /\.jpe?g$|\.gif$|\.png$/, loader: 'file?name=[name].[ext]'},
{test: /\.eot$|\.ttf$|\.svg$|\.woff2?$/, loader: 'file?name=[name].[ext]'}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': 'development'
},
'development': true
}),
new ExtractTextPlugin('[name].css')
]
};
My client.js file only contains this line (for the purpose of debugging this issue):
import React from 'react';
And here is the resulting bundle
Any values passed to webpack.DefinePlugin as strings are treated as code fragments—that is to say, using
new webpack.DefinePlugin({
ENV: "development"
});
with the code
console.log(ENV);
results in
console.log(development);
Instead, you want
new webpack.DefinePlugin({
ENV: "\"development\""
});
which will result in
console.log("development");
To fix your issue, change your plugins to
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': "'development'"
}
}),
new ExtractTextPlugin('[name].css')
]
I usually allow webpack to read from process.env.NODE_ENV so that React minifies properly when you run webpack with NODE_ENV=production:
new webpack.DefinePlugin({
"process.env": { NODE_ENV: JSON.stringify(process.env.NODE_ENV || "development") }
})