Using Webpack externals and still allow ES6 style imports? - javascript

I'm creating a Chrome Extension and I'm using React and Webpack.
Because this is a Chrome extension, I can use manifest.json to load React and ReactDOM into the browser well before any line of my own code get executed. My understanding is that:
the react.js lib loaded by manifest.json show up as globals, accessible via window.React
webpack externals can be configured so that React and ReactDOM doesn't get bundled
Here are my files:
webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'skinny-bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
externals: {
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react'
},
'react-dom': {
root: 'reactDOM'
}
}
}
manifest.json
{
"manifest_version": 2,
"name": "Test Application",
"version": "3.2",
"description": "testing",
"short_name": "some test",
"author": "blah blah",
"content_scripts": [
{
"matches": ["https://www.google.com/*"],
"js": [
"react.js",
"react-dom.js",
"skinny-bundle.js"
],
"run_at": "document_idle"
}
]
}
index.js
import HelloGreeting from './HelloGreeting'
ReactDOM.render(
<HelloGreeting />,
document.getElementById('cst')
)
HelloGreeting.js
// import React from 'react' <----- here is my problem!!!
// functional component test
const Hello = props => {
return (
<div>hello world</div>
)
}
// class component test
class HelloGreeting extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<ul>
<li><Hello /></li>
<li>hello universe</li>
</ul>
)
}
}
export default HelloGreeting
Here lies my problem. I have a simple React Component called HelloGreeting that doesn't work if I keep the import React from 'react' line. If I comment out the import, it actually works because my guess is that webpack just ignores it because React has been defined in webpack externals. If I leave the import statement in , I get a "Uncaught TypeError: Cannot read property 'Component' if undefined" error, possibly because Webpack tries to bundle and messing up with window.React somehow. I made this guess because of what webpack emits with or without the import.
If I comment out the import, webpack emits a smaller bundle:
Hash: 26f1526e2554c828c050
Version: webpack 3.5.5
Time: 80ms
Asset Size Chunks Chunk Names
skinny-bundle.js 5.75 kB 0 [emitted] main
[1] ./HelloGreeting.js 2.5 kB {0} [built]
+ 1 hidden module
If I keep my import, my webpack emits a larger bundle:
Hash: 1fc9353f1fe6dd935744
Version: webpack 3.5.5
Time: 77ms
Asset Size Chunks Chunk Names
skinny-bundle.js 6.05 kB 0 [emitted] main
[1] ./HelloGreeting.js 2.71 kB {0} [built]
+ 2 hidden modules
My question is, what can I do to configure webpack so that I still have my ES6 style imports and still have webpack NOT bundle react.js?
I really want to keep the import statement because these components will be used in future projects and I want to keep this as modular and portable as possible.

It turns out I was over-complicating webpack externals.
Changing from:
externals: {
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react'
},
'react-dom': {
root: 'reactDOM'
}
}
to:
externals: {
react: 'React',
'react-dom': 'ReactDOM'
},
...solved my problem. Now webpack ignores all of my React code and keeps my bundle small.
https://github.com/webpack/webpack/issues/1275 helped me out.

Related

Rollup doesn't bundle all the files exported in my input src

I have a problem. I'm currently making a component library for react, it works perfectly in storybook. But when I do a npm rollup, or install my package from npm. I look inside the cjs and esm folders and not all my necessary files are there.
Here are my config files:
const packageJson = require("./package.json");
export default [
{
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
},
],
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
],
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()],
},
];
rollup.config.ts
export * from "./components";
export * from "./core";
export * from "./themes";
export * from "./hooks";
src/index.ts
export { default as GlobalTheme } from "./global-theme";
export { default as LightTheme } from "./light-theme";
export { default as DarkTheme } from "./dark-theme";
themes/index.ts
When I try to use my library in another project it gives me a lot of compile errors :
Failed to parse source map from '...\node_modules\#gitname\LIBRARY_NAME\src\themes\light-theme.ts' file: Error: ENOENT: no such file or directory
and the same message for every single component I created in the library.
I'm honestly stuck, I tried every solution on the internet none worked.
Thank you in advance for your help
It has to do with way you are importing your components. I had exactly the same issue with the same configuration as you have.
In my case I had something like:
{
input: "dist/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()],
}
When you are using the components you have to import from where the actual bundled files are. In my case it was in dist folder. so just import it like so,
Import {Button} from "#yourlib/your-lib/dist"

Individual component exports with Rollup instead of one massive index.js file?

I’m working on a proprietary component library built with React and using Rollup to bundle everything. Currently, it’s bundling everything into these files:
dist
├ cjs
│ └ index.js (1.7mb)
└ esm
└ index.js (1.7mb)
My hope is that I could instead have every component be bundled individually so the consuming app doesn’t have to download a huge index.js file, but this may be where my inexperience with Rollup kicks in.
I currently have a single entrypoint for Rollup:
input: [
'src/index.js',
],
My index.js file looks something like this (but with many more components):
import { Badge } from './components/Badge';
import { Button } from './components/Button';
import { CardFooter } from './components/CardFooter';
import { CardHeader } from './components/CardHeader';
import { CardTagList } from './components/CardTagList';
import { CardToolbar } from './components/CardToolbar';
import { CartAnimation } from './components/CartAnimation';
export {
Badge,
BasePrice,
Button,
CardFooter,
CardHeader,
CardTagList,
CardToolbar,
CartAnimation,
};
What do I have to do to ensure that components are each bundled separately and can still be imported in the apps that use the library with:
import { Button } from '#company/component-library';
Here is my full config as it stands today:
import { babel } from '#rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';
import commonjs from '#rollup/plugin-commonjs';
import dynamicImportVars from '#rollup/plugin-dynamic-import-vars';
import eslint from '#rollup/plugin-eslint';
import json from '#rollup/plugin-json';
import resolve from '#rollup/plugin-node-resolve';
import stylelint from 'rollup-plugin-stylelint';
import styles from 'rollup-plugin-styles';
require('dotenv').config();
export default {
external: [
'react',
'react-dom',
'styled-components',
],
input: [
'src/index.js',
],
output: [
{
// Bundle into ESM for modern consumers.
// Only ESM build can currently be tree-shaken.
dir: 'dist/esm',
format: 'esm',
},
{
// Bundle into CJS for other consumers.
dir: 'dist/cjs',
format: 'cjs',
},
],
plugins: [
eslint({
include: '**/*.js',
throwOnError: true,
}),
stylelint(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
}),
resolve({
browser: true,
}),
styles(),
commonjs(),
json(),
dynamicImportVars({}),
terser(),
],
};
Note: Probably not important, but this project is published to npm as a private repo, but currently the app that uses it installs it using a commit hash.
You could try adding the preserveModule
export default {
preserveModules: true,
...
}
I have done some tweek to the rollup config. My problem statement was to export custom elements seperately. Find my custom solution below:
function generateComponentConfig() {
const dir = fs.readdirSync("./src/web-components");
return dir.map(folderName => {
return {
input: [`src/web-components/${folderName}/index.js`],
output: [
{ file: `public/build/wc/${folderName}.mjs`, 'format': 'es' },
{ file: `public/build/wc/${folderName}.js`, 'format': 'umd', name: folderName }
],
plugins: [
svelte({ compilerOptions:{customElement: true}, emitCss: false, include: /\.wc\.svelte$/ }),
svelte({ compilerOptions: {customElement: false}, emitCss: false, exclude: /\.wc\.svelte$/ }),
resolve()
]
};
})};
Use the above function in your export as below
export default [
...generateComponentConfig(), {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js',
},
plugins: plugins(),
watch: {
clearScreen: false,
}}];

Webpack bundle is not exported and can't be imported

I created my react Project A using Create-React-app. Then I bundle it them with Webpack and saved in my Git account.
Now I create another project(Called it Project B)in different directory. Download Project A directly from git. And trying to use it like so:
import React from 'react';
import ReactDOM from 'react-dom';
import { Main } from 'project-A/dist/main'
ReactDOM.render(<Main />, document.getElementById('root'));
I am getting an error like following:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
The webpack from Project A looks like this:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebPackPlugin = require("html-webpack-plugin");
const nodeExternals = require("webpack-node-externals");
module.exports = [
{
/*Client Side*/
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
use: {
loader: "html-loader",
options: { minimize: true }
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,"css-loader"]
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./public/index.html",
filename:"./index.html"
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename:"[id].css"
})
]
}
]
I have research through the github and tried to change the name import, it still does not work.
Project A's component looks like this:
App.js:
render() {
return (
<div>
{this.renderCodeAuthCard()}
{this.renderConfirmCard()}
{this.renderVerifyCard()}
</div>
);
}
export default App;
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
Apparently webpack is not exporting the bundle file that is created in Project A. Since the import yields "undefine".
I am trying to find a way to export my webpack bundle file and use it in another project.
Any help will be appreciated
Its because you are not exporting any thing from index.js of Project A. The libraries installed by npm export functions from index.js.

No loading/serving of stylesheets - react

I am attempting SSR with React router's StaticRouter.
express.js (server)
const html = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
res.status(200).send(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/app.css" type="text/css"/>
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`);
Serving of static files:
app.use(express.static(path.resolve(__dirname, "../dist/client")));
App.js (shared)
import React from "react";
import { Switch, Route } from "react-router";
export default () => {
return (
<Switch>
...
</Switch>
);
};
index.jsx (client)
import React from "react";
import { BrowserRouter } from "react-router-dom";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById("app")
);
./styles/Main.scss
.header {
background-color: #002933;
}
I have 2 webpack configurations, 1 for the client & 1 for the server:
webpack.config.dev.js
const nodeExternals = require("webpack-node-externals");
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
devtool: "cheap-module-eval-source-map",
entry: {
app: [
"eventsource-polyfill",
"webpack-hot-middleware/client",
"webpack/hot/only-dev-server",
"react-hot-loader/patch",
"./client/index.jsx",
],
vendor: [
"react",
"react-dom",
],
},
output: {
path: `${__dirname}/dist/client`,
...
},
...
module: {
loaders: [
...
}, {
test: /\.scss$/,
exclude: /node_modules/,
loader: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: "css-loader",
query: {
localIdentName: "[hash:8]",
modules: true
}
}, {
loader: "postcss-loader"
}, {
loader: "sass-loader"
}
]
}),
},
],
},
plugins: [
new ExtractTextPlugin({
filename: "[name].css",
allChunks: true
}),
]
};
webpack.config.server.js
const ExternalsPlugin = require("webpack-externals-plugin");
module.exports = {
...
output: {
path: `${__dirname}/dist/`,
filename: "server.bundle.js",
},
...
resolve: {
...
modules: [
"client",
],
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: "babel-loader",
}, {
test: /\.scss$/,
loader: 'style-loader!css-loader/locals?module&localIdentName=[name]__[local]___[hash:base64:5]!sass-loader',
},
],
},
plugins: [
new ExternalsPlugin({
type: "commonjs",
include: `${__dirname}/node_modules/`,
}),
],
};
I have a JSX file where the .header should be applied to:
import React from "react";
import Links from "./Links.jsx";
import profilePic from "../../img/brand/profilePic.jpg";
import styles from "../../styles/Main.scss";
export default class Header extends React.Component {
constructor() {
super();
}
render() {
return (
<header className={styles.header}>
<img src={profilePic} alt="Professional Picture"/>
<h5>{this.props.pageName}</h5>
<Links/>
</header>
);
}
}
This throws the error:
TypeError: Cannot read property 'header' of undefined
at Header.render (E:/Documents/Projects/website/client/js/components/Header.jsx:22:30)
at resolve (E:\Documents\Projects\website\node_modules\react-dom\cjs\react-dom-server.node.development.js:2149:18)
at ReactDOMServerRenderer.render (E:\Documents\Projects\website\node_modules\react-dom\cjs\react-dom-server.node.development.js:2260:22)
at ReactDOMServerRenderer.read (E:\Documents\Projects\website\node_modules\react-dom\cjs\react-dom-server.node.development.js:2234:19)
at Object.renderToString (E:\Documents\Projects\website\node_modules\react-dom\cjs\react-dom-server.node.development.js:2501:25)
at E:/Documents/Projects/website/server/config/lib/express.js:204:31
at Layer.handle [as handle_request] (E:\Documents\Projects\website\node_modules\express\lib\router\layer.js:95:5)
at trim_prefix (E:\Documents\Projects\website\node_modules\express\lib\router\index.js:317:13)
at E:\Documents\Projects\website\node_modules\express\lib\router\index.js:284:7
at Function.process_params (E:\Documents\Projects\website\node_modules\express\lib\router\index.js:335:12)
at next (E:\Documents\Projects\website\node_modules\express\lib\router\index.js:275:10)
at p3p (E:\Documents\Projects\website\node_modules\lusca\lib\p3p.js:15:9)
at E:\Documents\Projects\website\node_modules\lusca\index.js:59:28
at xframe (E:\Documents\Projects\website\node_modules\lusca\lib\xframes.js:12:9)
at E:\Documents\Projects\website\node_modules\lusca\index.js:59:28
at xssProtection (E:\Documents\Projects\website\node_modules\lusca\lib\xssprotection.js:16:9)
When running the application, webpack reports that the stylesheet has been loaded:
EDIT
Other than an ES6 import, I have attempted to use CommonJS' require() as in MERN but still no look...
When I build my server webpack config, I am now getting the error:
ERROR in (webpack)-dev-middleware/node_modules/mime/index.js
Module not found: Error: Can't resolve './types/standard' in 'E:\Documents\Projects\website\node_modules\webpack-dev middleware\node_modules\mime'
# (webpack)-dev-middleware/node_modules/mime/index.js 4:26-53
# (webpack)-dev-middleware/index.js
# ./server/config/lib/express.js
# ./server/config/lib/app.js
# ./server/server.js
I am not sure if this a red-herring or not in this situation or not but thought it worth mentioning here as I am quite lost. Feel as though I am clutching at straws at this point.
This is my .babelrc:
{
"presets": [
"react",
"es2015",
"stage-0"
],
"plugins": [
"react-hot-loader/babel",
"transform-decorators-legacy"
],
"env": {
"server": {
"plugins": [
[
"css-modules-transform", {
"preprocessCss": "./loaders/sass-loader.js",
"generateScopedName": "[hash:8]",
"extensions": [".scss"]
}
]
]
},
"production": {
"presets": [
"es2015",
"react",
"react-optimize",
"es2015-native-modules",
"stage-0"
]
}
}
}
I was attempting to go back to basics and have my babel handle server-side bundling instead of webpack. This was built from a tutorial for SSR with CSS modules I was kindly linked to by #mootrichard
EDIT 2
A few observations which might help...when using an es6 import for stylesheets:
import styles from "../../styles/Main.scss";
and log styles into the console, it returns undefined (evidence that it cannot find the file for some reason).
When putting the <link> tag in the head for the initial page, the <link> tag is present in the markup but not in the network:
However, when navigating to localhost:8000/app.css, a positive response with the styling is sent back:
If the browser can find the bundled version standalone, then why is it not being loaded in my initial page? (The path is correct)
You're having issues because you're using css-loader/locals but not using ExtractTextPlugin (at least in Development).
https://github.com/webpack-contrib/css-loader/issues/59
Note: For prerendering with extract-text-webpack-plugin you should use css-loader/locals instead of style-loader!css-loader in the prerendering bundle. It doesn't embed CSS but only exports the identifier mappings.
This also explains why you're not able to access the style variable .theHeader.
Also, the error Resource interpreted as Stylesheet but transferred with MIME type text/html: "http://localhost:3000/app.css". is a red herring. That is simply the error message you receive if you try to load a stylesheet that doesn't even exist. Which in this case, doesn't appear to be in the directory that you think it is, or isn't actually being generated into a file there.
Since ExtractTextPlugin is disabled in development, its likely that your CSS is only being processed by css-loader/locals. This might not be a problem in production, since it pairs with ExtractTextPlugin but could explain your problems of running this in development.
Update:
In looking into this over a little more, I came across a blog post that I think might help you figure out how to configure your CSS to work how you want. https://medium.com/#mattvagni/server-side-rendering-with-css-modules-6b02f1238eb1
I think the main reason for the complication here is that you're sending over the HTML as a rendered string via ReactDOMServer. So there is no where for webpack to inject a <link> tag into. You might want to consider just having a <link> tag in your header to reference your desired CSS file, since webpack is going to create a single CSS file anyways.
Finally, I highly recommend studying a bit more on webpack, especially since SSR is a newer process and requires doing things a bit differently than many have initially anticipated when webpack was first created.
I think you have an error in your webpack.config json hierarchy for you css/sass loaders. Replace your "loaders" array under module with this "rules" array:
module: {
rules: [
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: ['css-loader', 'sass-loader']
}),
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
],
},
You can see more examples of ExtractTextPlugin usage here: https://github.com/webpack-contrib/extract-text-webpack-plugin#usage

Transpile to ES5 including files outside root directory

I have a project with multiple apps and some common react components in this structure:
ui
- app1
- app2
- components
The javascript files in components are written in ES6. Both app1 and app2 have this webpack config:
var path = require('path')
module.exports = {
entry: ['babel-polyfill', './src/index.js'],
module: {
rules: [{
test: /\.jsx?$/,
include: [
path.resolve(__dirname, '../components'),
path.resolve(__dirname, 'src')
],
loader: 'babel-loader'
}]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
alias: {
InputComponents: path.resolve(__dirname, '../components'),
}
},
}
Babel config:
{
"plugins": [
"transform-async-to-generator",
"glamor/babel"
],
"presets": [
"es2015",
"react",
"stage-0"
]
}
My code:
import React, { Component } from 'react'
import { Button } from 'CommonComponents'
export default class ButtonView extends Component {
render () {
return <Button>GO!</Button>
}
}
I want my components and my apps to be built and transpiled to ES5 using Babel. The apps work but not the external components and I get errors like:
Module build failed: SyntaxError: Unexpected token (6:34)
4 | class Button extends Component {
5 | render () {
> 6 | if (this.props.stroke) return <ButtonStroke {...this.props} />
| ^
How can I make sure that babel transpiles my external components?

Categories