Watch local dependencies with webpack-dev-server - javascript

I’m creating a Javascript library. Inside my project there’s a folder that contains examples of how to use the library. Inside each of the examples is a webpack config file with the entire purpose of bundling that example and serving it over webpack-dev-server with hot reloading. Each of these examples also has the library (at the root of the project) listed as a local NPM dependency. I have hot reloading working for each example and I have babel compiling the library at the root on a watch command.
Primary question: Is there a way that I can have the hot reloader of webpack-dev-server respond to changes in that local NPM dependency?
Secondary question: Is this intended to be the default behavior of webpack? If so, what is could be wrong with my machine/config file?
General/vague question: Am I doing this wrong? I feel like it should be a lot easier to serve local examples (I’m not interested in using Storybook either as the examples I’m writing aren’t in React, Vue, Angular, etc... it's all straight-up vanilla Javascript).
Here's my webpack.config.js file:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, argv) => ({
mode: argv.mode,
entry: './index.js',
output: {
path: path.join(__dirname, '/dist'),
filename: 'index.bundle.js'
},
devtool: argv.mode === 'development' ? '#eval-source-map' : 'source-map',
devServer: {
port: 8080,
hot: true,
open: true,
stats: {
children: false, // Hide children information
maxModules: 0 // Set the maximum number of modules to be shown
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [new HtmlWebpackPlugin({ template: './index.html' })]
});
And my package.json file (note that syft.js is the local dependency I want to watch for changes):
{
"name": "with-grid",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --mode development",
"build": "rm -rf dist && webpack --mode production",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "",
"devDependencies": {
"babel-loader": "^8.0.6",
"html-webpack-plugin": "^3.2.0",
"path": "^0.12.7",
"webpack": "^4.39.1",
"webpack-cli": "^3.3.7",
"webpack-dev-server": "^3.7.2"
},
"dependencies": {
"syft.js": "file:../.."
}
}
Folder structure is like such:
dist
index.js (Babel-generated file, what package.json points to, and the file that I want to be watched)
examples
with-grid
webpack.config.js (referenced above)
package.json (referenced above)
src
index.js (main src file)
Operating System: MacOS 10.14.6
Browser: Chrome 76
Node: 12.8.0
NPM: 6.10.3
Yarn: 1.17.3

I opted for a different strategy that now seems very obvious in retrospect. Rather than treating my library like a local node dependency that needs to be resolved, I can just simply import it locally.
When importing from my example folder, I do:
import syft from '../../src'; // Like a relative file
Instead of:
import syft from 'syft.js'; // Like an NPM package
After this small change, everything reloads as expected.

You can use npm link to create symbolic links between your React app and your local dependencies. Just make sure you build your local dependency to trigger reload in your React app.
Your local dependency should have a "main" and a "name" attributes in the package.json. webpack-dev-server will reload based on changes in your "main"
{
"name": "my-dep",
"main": "lib/index.js",
}
Run npm link next to the dependency package.json.
Run npm link my-dep in your React project - it will create a symbolic link between the two projects.
import myDep from 'my-dep in your React project. Reload will be triggered when you change lib/index.js
You can read more here https://medium.com/dailyjs/how-to-use-npm-link-7375b6219557
npm link: https://docs.npmjs.com/cli/link

Related

Why importing from a linked local ES modules package using the package name works with "main" property but fails with "module"

Question
Why does importing from a linked local NPM pacakage (built as an ES module) using the pacakge name works when the pacakge.json has the "main" property, but fails when it has the "module" property?
Setup
More spicifically, if we have the following setup:
exporter: A Node.js package which is an ES module that exports something (e.g., using export default).
importer: A Node.js module that tries to import what exporter exports, using import something from 'exporter'.
Using npm link to locally link exporter to importer.
Then:
The setup runs successfully if exporter's package.json uses the main property.
The setup run fails if exporter's package.json uses the module property.
This failure can be "fixed" by using import something from 'exporter/dist/bundle.js', but that's unacceptable.
Why is that? I guess I'm doing something wrong?
For some more info about this setup, see my other question
Code
exporter
|- webpack.config.js
|- package.json
|- /src
|- index.js
|- /dist
|- bundle.js
webpack.config.js:
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default {
mode: "development",
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
library: {
type: "module",
},
},
experiments: {
outputModule: true,
},
};
package.json:
{
"name": "exporter",
"version": "1.0.0",
"main": "dist/bundle.js", <-- *** NOTE THIS LINE ***
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0"
},
"type": "module"
}
index.js:
function util() {
return "I'm a util!";
}
export default util;
importer
|- package.json
|- /src
|- index.js
package.json
{
"name": "importer",
"version": "1.0.0",
"type": "module"
}
index.js
import util from 'exporter';
console.log(util());
Then:
Linking:
⚡ cd exporter
⚡ npm link
⚡ cd importer
⚡ npm link exporter
Executing:
⚡ node importer.js
I'm a util!
However, if exporter's package.json is changed to:
{
"name": "exporter",
"version": "1.0.0",
"module": "dist/bundle.js", <-- *** NOTE THIS LINE ***
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0"
},
"type": "module"
}
Then:
Executing:
⚡ node importer.js
Fails:
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'importer\node_modules\exporter\' imported from importer\src\index.js
But Why?
When resolving a node_modules package, Node.js first checks if there's a "exports" field in the package.json, if there's none it looks for a "main" field, and if it's. also not there, it checks if there's a file named index.js – although this behavior is deprecated and may be removed in a later version of Node.js, I would advise against relying on it and always specify "exports" and/or "main" fields. You can check out Package entry points section of Node.js docs to get more info on that.
"module" is simply not a field Node.js uses, it's used by some other tools so it's certainly okay to have it defined in your package.json, but you should also have "main" and/or "exports" fields. Node.js will use the file extension to determine if the file is an ES module (dist/bundle.js is using .js as extension and you have "type": "module" in your package.json, so you're all set).

Build index.html with webpack to use with dev server

Im using webpack for running my Three.js application with the following configuration in the webpack.config file:
module.exports = {
entry: `${__dirname}/src/tut15.js`,
output: {
path: __dirname + '/dist',
filename: 'index_bundle.js'
},
devtool: 'inline-source-map',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
},
plugins: [
new HtmlWebpackPlugin()
]
}
package.json
"scripts": {
"start": "webpack-dev-server --open",
"build": "webpack --config webpack.config.babel.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
With the HtmlWebpackPlugin it autogenerates the html for me. But since I want to create a custom html index file with some new tags this doesnt work for me. So I build the project
npm run-script build
and runs the index.html in the dist folder manually with my applied edits and everything works.
If I remove the HtmlWebpackPlugin from the webpack.config and build the project again and then run:
npm start
livereload works for my js files together with my new index.html with custom tags in the distfolder.
Questions:
It doesnt feels correct to create the changes in the dist folder. How can I generate a index.html straight from source? I guess that might solve all my problems being able to run dev server with a custom index.html
Or is it possible to get some livereload for the build as well?
after I have built my project it generates index.html and index.bundle in my distfolder. If I remove the index.bundle the project works anyway. What exactly does index.bundle do?
What is the best approach or do I need to build my project each time Im doing an update in my index file?
Thanks!
For questions regarding custom index.html and hot-reloading at least.
The plugin has some config you can add for templating.
new HtmlWebPackPlugin({
filename: 'index.html',
template: './public/index.html',
}),

build error with webpack

I'm getting an error when i ran webpack and i can't seem to find the reason why.
Entrypoint main = bundle.js
[0] ./client/client.js 224 bytes {0} [built] [failed] [1 error]
[1] multi ./client/client.js 28 bytes {0} [built]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
ERROR in ./client/client.js
Module build failed: TypeError: Cannot read property 'babel' of undefined
at Object.module.exports (/Users/XXX/Desktop/react-training/react-todo-list/node_modules/babel-loader/lib/index.js:103:36)
# multi ./client/client.js
here is my package.json,
{
"name": "react-todo-list",
"version": "1.0.0",
"description": "A simple todo list app built with React, Redux and Webpack",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "nodemon server/server.js"
},
"repository": {
"type": "git",
"url": "https://github.com/kweiberth/react-todo-list.git"
},
"author": "XXXX",
"license": "ISC",
"dependencies": {
"babel-core": "^6.4.5",
"babel-loader": "^6.2.2",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"express": "^4.13.4",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"webpack": "^1.12.13"
}
}
here is my webpack.config,
module.exports = {
entry: ['./client/client.js'],
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
publicPath: '/',
},
module:{
rules:[
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['react', 'es2015']
}
}
]
}
}
I've tried installing different versions of babel and webpack with no luck. I'm following an old tutorial on youtube and I was wondering if the older version mentioned in the package.json causes it. This is my initial days learning react and getting use to the node stack. It'd be great if someone can lend me a helping hand to figure this out.
you are using a very old version of webpack (1.12.13) ... we are already in the version 4xx range. It can be really hard to get this all set up in the beginning. I made the same mistake of thinking, that I can set it all up by myself and in the end I spent weeks scratchng my head and not writing any code at all.
This doesn't really answer your question, but...
I would really recommend using something to get you up und running quickly, like create-react-app. If, by any chance, you are still curious how the whole bundling and transpiling stuff works then you can look into that later. But for now, this whole stuff might just kill your motivation. Keep it up! Eventually everything will come together.
Update
Ok, in case you really want to get to the bedrock. The first thing you should do is update your packages. Especially Webpack. You also need webpack-cli when you want to use webpack v4. Furthermore I would suggest reading the webpack getting started section. There are many good examples on how to set up your webpack config. Link to Webpack Docs
I would just recommend using create-react-app, You will not need to bother with build tools. Especially since you're learning React, not the various build tools that accompany it.
npx create-react-app my-app
cd my-app
npm start
(npx comes with npm 5.2+ and higher, see instructions for older npm versions)
If you don't want to do that, update both Babel, Webpack and React.

How can I export a React Component as an NPM package to use in separate projects?

I have created a React component inside a project that I'd like to use in multiple projects. At the moment, I only care about doing this locally and for development. The React Component is rendered into the root div, the project uses webpack and babel to transpile JSX, ES6 and some ES7 features into a bundle.
I thought it would be simple to export this component such that I can simply run npm install MyComponent and begin using it in a fresh project. However, I find it isn't so straight forward. In particular, I've been reading for hours and hours and only seem to be getting more confused.
If my end goal is to keep developing 'MyComponent' in its containing project, while using 'MyComponent' in any number of other local projects, what are my options? The first thing I did was change the main key of my package.json to /src/components/MyComponent and run npm pack. This produces a tgz file I can install via its absolute filepath in other projects. However, I found that the es6 and jsx was not being transpiled and so my client projects would be unable to parse MyComponent. I then used webpack to transpile into lib/MyComponent, but when I have import MyComponent from './path/to/MyComponent-1.0.0.tgz I'd only see {} (an empty object) in the console.
Searching for solutions to my problem turn up many different approaches pulling together NPM, Grunt, Gulp, Babel, Webpack, etc.. And I am worried it will be many many more hours (days?) before I can grind that down to something understandable.
Given my requirements, what is the simplest solution I can implement to 1) compile down my React Component to the simplest to import module 2) import it into any local projects 3) continue to develop the package in the original host project and have changes easily propagate to client projects.
In general, if you're going to begin creating React components as separated packages (which is a great pattern, for all the reasons you've already mentioned) - you're going to need to get at least a bit familiar with webpack and babel. There's a ton to learn here, but let me try to point you in the right direction:
// webpack.config.js
/* eslint-disable */
const path = require('path')
const webpack = require('webpack')
const ENVIRONMENT = process.env.NODE_ENV
const PRODUCTION = ENVIRONMENT === 'production'
const SOURCEMAP = !PRODUCTION || process.env.SOURCEMAP
const library = 'your-lib-name' // << RENAME THIS <<
const filename = PRODUCTION ? `${library}.min.js` : `${library}.js`
const plugins = []
if (PRODUCTION) {
plugins.push(
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(ENVIRONMENT),
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.UglifyJsPlugin({
minimize: true,
output: { comments: false, semicolons: false },
sourceMap: SOURCEMAP,
})
)
}
module.exports = {
devtool: SOURCEMAP ? 'source-map' : 'none',
entry: `${__dirname}/path/to/your/component.js`, // << RENAME THIS <<
externals: {
'react': 'react',
'react-dom': 'react-dom',
},
module: {
loaders: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
}],
},
output: {
filename,
library,
path: `${__dirname}/lib`,
libraryTarget: 'umd',
umdNamedDefine: true,
},
plugins,
}
I know that looks like a bunch - but it handles the majority of what you're going to want. In specific:
If you specify NODE_ENV=production when building, this will uglify/minify your package, and do some other trimming which you may want later.
Building with this script will output a sourcemap, which you can use with dev tools to inspect your minified code in the debugger window, among other things.
This marks react and react-dom as externals - which means they won't get bundled up and packaged inside your bundle. This is great - because it means you won't get 2+ copies of react's filesize just because you've imported your own component!
To use it, though, you now need some package.json love.
package.json
{
"name": "Your Name",
"version": "0.0.1",
"description": "This is my awesome react package!",
"main": "path/to/your/component.js",
"author": "Your Name",
"license": "MIT",
"repository": { /* Your Repo Info Here */ },
"dependencies": {
"any-packages-you-need-included-in-builds": "^1.0.0"
},
"devDependencies": {
"babel-cli": "^6.22.2",
"babel-loader": "^7.1.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.22.0",
"prop-types": "^15.5.10",
"react-dom": "^15.6.1",
"webpack": "^3.0.0"
},
"scripts": {
"build": "yarn prebuild && NODE_ENV=production webpack",
"prebuild": "mkdir -p ./lib && rm -rf ./lib/*"
}
}
Obviously, you can have a lot more here if you need it - such as other babel-plugin-* plugins that you use in your transpilation, other packages, etc.But this set will let your webpack build run. Note that the scripts here assume you're using yarn - so that you can run yarn build, and that you're on a posix system, for mkdir to work. If you're on windows or not using yarn, just update the scripts accordingly.
The rest is just learning to publish your package to npm or another package repository. Primarily, that's just setting the version number in package.json to something new (npm version) and then publishing (npm publish). You will have to have an npm account for this, of course - and be logged in (npm login).
Once you've published to npm you can just yarn add your-package-name.
Remember, though - we marked react and react-dom as external - so in the consuming package, you'll need to make sure they're available as window.React and window.ReactDOM - or you'll need to include the component directly from node_modules/your-package-name/path/to/your/component.js
You don't need to npm pack a package to use it. If you make your component into a git repo and put it on Github, you can use NPM to install it directly from Github by using npm install alex/mycomponent where alex is your github username and mycomponent is the repo name. Re-running that command will re-install from Github, in case you make changes to the repo.
Once you're happy with the component, you can upload it to the NPM registry to install like any other package (npm install name). Using Github at first makes it a bit easier to develop.
Webpack might not compile things from node_modules by default. Usually, packages are pre-compiled before being published anyway, but you should be able to configure webpack to build your 'packaged' component, along with the rest of your app. Maybe this will help: https://stackoverflow.com/a/38008149/7486612
In order to push react libraries into NPM, you may need some boilerplate which will install and convert many things for you (and you can still use your current react module as the main source, just follow the guides at the end of my answer, then you will surely get all the ideas)
Or you can also refer to my previous answer to a similar question:
Issue with publishing to npm
=====
I've also pushed several react libraries successfully into NPM:
https://www.npmjs.com/~thinhvo0108
=====
Your github repositories' folder structure should also look like mine:
https://github.com/thinhvo0108/react-paypal-express-checkout
=====
Useful tutorial below here:
(boilerplate source) https://github.com/juliancwirko/react-npm-boilerplate
(author's article) http://julian.io/creating-react-npm-packages-with-es2015/
Start by looking at existing component library, eg Material UI.
Specifically check out npm scripts they have (see package.json):
"build:es2015": "cross-env NODE_ENV=production babel ./src --ignore *.spec.js --out-dir ./build",
"build:es2015modules": "cross-env NODE_ENV=production BABEL_ENV=modules babel ./src/index.js --out-file ./build/index.es.js",
"build:copy-files": "babel-node ./scripts/copy-files.js",
"build:umd:dev": "webpack --config scripts/umd.webpack.config.js",
"build:umd:prod": "cross-env NODE_ENV=production webpack --config scripts/umd.webpack.config.js",
"build": "npm run build:es2015 && npm run build:es2015modules && npm run build:copy-files && npm run build:umd:dev && npm run build:umd:prod",
That's example of very involved and high quality component library, that makes sure that regardless of your build pipeline you'll be able to use it.
Setting up build process with webpack might be cumbersome, but don't concentrate on that too much from the begining, and cover cases that are most straight forward to you.
Also check out https://storybook.js.org/ while working on your components.

Webpack: How to compile, write on disk and serve static content (js/css) using webpack-dev-server

I want to build my js/css code, write it on disk and serve it using webpack-dev-server in a single command. I don't want to set-up another server for production mode. How do we do it? Sharing my webpack.config.js file content below:
module.exports = {
watch: true,
entry: [
'./src/index.js'
],
output: {
path: __dirname +'/dist/',
publicPath: '/dist/',
filename: 'bundle.js'
},
module: {
loaders: [
{
exclude:/(node_modules)/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
]
},
devServer: {
historyApiFallback: true,
contentBase: './'
}
};
Please find the start script used in package.json below:
"start": "webpack-dev-server --progress --colors"
I am running "npm run start". Please help.
New webpack-dev-server is released, and it supports writeToDisk option.
devServer: {
writeToDisk: true
}
With webpack-dev-server v4.0.0+, devMiddleware is used:
devServer: {
devMiddleware: {
writeToDisk: true
}
}
webpack-dev-server uses a "virtual" Express server in conjunction with Sock.js to emulate a server running on your machine. Regarding compilation, webpack-dev-server does recompile the bundle whenever it detects code changes. This recompilation is served from memory, however, as opposed to the project's build directory (or, in your case, the dist directory). From the docs:
Using this configuration, webpack-dev-server will serve the static files in your build folder. It’ll watch your source files, and recompile the bundle whenever they are changed.
Regarding writing to your disk, webpack-dev-server does not do this. This is partially addressed by what's been written above. Additionally, note the following, also from the Webpack docs:
This modified bundle is served from memory at the relative path specified in publicPath (see API). It will not be written to your configured output directory. Where a bundle already exists at the same URL path, the bundle in memory takes precedence (by default).
To write to your disk, use the ordinary webpack module. Of course, as your question hints at, manual recompilation after every change is tedious. To address that chore, include the watch flag. From the Terminal, you could execute the command:
$ webpack --watch
I prefer to delegate this to an NPM script, however. Note that the -w flag below is equivalent to writing --watch.
// NPM `scripts` field:
"watch": "webpack -w"
If you want to run webpack-dev-server while also having your changes get recompiled and written to your output directory, you can add an additional NPM script like so:
"scripts": {
"serve": "npm run watch && npm run start",
"start": "webpack-dev-server --progress --colors",
"watch": "webpack -w"
}
Then, in your Terminal, just execute $ npm run serve to accomplish this.
If you're interested in the added convenience of automatic reload, you can do so by defining the following within the plugins field of your Webpack config file:
new webpack.HotModuleReplacementPlugin()
Note: This will likely require additional configuration settings for Babel. If I were you, I would take out the query field from your babel loader and, instead, delegate your Babel configuration to an external .babelrc file. A good one to use that is compatible with hot reloading might look like this:
{
"presets": ["env", "es2015", "react"],
"plugins": ["react-hot-loader/babel"]
}
On a side note, I've created a boilerplate repo for easily starting out projects with my desired structure. The Webpack configuration may of interest to, specifically. In particular, it employs Webpack 2 and includes other build tools like Babel (for transpilation), ESLint (syntax checker) as well as support for CSS/Sass and a variety of other file formats.
You can change your start script definition to this:
"start": "webpack --watch & webpack-dev-server --inline --progress --colors".
This sends the webpack watch-and-rebuild process to the background so that you can also hot-reload your modified modules as you change them with webpack-dev-server.
EDIT:
Either of these plugins may do what you want:
https://github.com/gajus/write-file-webpack-plugin
https://github.com/FormidableLabs/webpack-disk-plugin
webpack-dev-server serve files from memory, you can replace webpack-dev-server with webpack-simple-serve, it use webpack's watch feature, write the compiled files to disk and use serve-handler to serve.

Categories