Related
The Problem
Over the last year and a half I have been developing a components library using Storybook, React and Webpack 5 for my team. Recently we have started to look at Next.JS and have a major project well underway using its framework. This however has created some challenges, as next.js renders Server-side and Client-side, meaning any imports that use client-side exclusive objects/functions etc. cause an error. Now this can be solved using dynamic imports, but that then creates loading times or missing content if not handled correctly.
Self Is Not Defined Error
Our whole components library causes this SSR error. It doesn't matter if you are importing a button or a popover that actually utilises window, you have to use dynamic imports. This then creates loading times and missing content on the rendered page. We can't even use the loading component in the library, as that needs loading. We also have the problem, that even if we took out all references to window or document in our code, some of our dependencies reference them somewhere and we just can't avoid it.
What we would like to be able to do with the library is import it in several ways to isolate window and document calls to their individual components, so we can avoid dynamic loading wherever possible.
import { Component } from 'Library'
import { Component } from 'Library/ComponentCategory'
import { Component } from 'Library/Component'
The reason behind the three imports is simple:
We want to be able to import the whole
library and any components we need from it. Other than in Next.JS this is not an issue. In Next.JS we would never import this way.
We want to be able to import a components category, so if we are using multiple components from that category we can import them with one import, not several. i.e Form components. This should only import the code and modules it requires. If a category doesn't reference client exclusive code then it should be able to be imported normally.
We want to be able to import an individual component, that only brings along the code and modules it needs, so if we need to dynamically import, we do it on an individual basis, not library wide.
This way of importing has been implemented, but no matter which route you take, it still fires the Next.JS 'self is not defined' error. This seems to mean, that even on an individual component import, the entire code base of the library is still referenced.
Attempted Solutions
Window Document Checks and Removal of Unneeded References
We removed any unneeded references to client exclusive code and added conditional statements around any statements we could not remove.
if (typeof window !== 'undefined') {
// Do the thing with window i.e window.location.href = '/href'
}
This didn't have any effect, largely due to the nature of the npm ecosystem. Somewhere in the code, document, screen or window is called and there isn't a lot I can do about it. We could wrap every import in this conditional, but lets be honest, that is pretty gross and likely wouldn't solve the problem without other steps being taken.
Library splitting
Using webpack 5 entry, output and splitChunks magic has also not solved the problem.
The first step was to configure entry and output. So I set my entry to something like this:
entry: {
// Entry Points //
//// Main Entry Point ////
main: './src/index.ts',
//// Category Entry Points ////
Buttons: './src/components/Buttons', // < - This leads to an index.ts
...,
//// Individual Component Entry Points ////
Button: './src/components/Buttons/Button.tsx',
OtherComponent: '...',
...,
},
And my output to:
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js',
library: pkg.name,
libraryTarget: 'umd',
umdNamedDefine: true,
},
This has allowed us to now import the library as a whole, via categories or as individual components. We can see this, as in the dist folder of the library, there are now Component.js(.map) files. Unfortunately this has still not allowed us to creep past the SSR error. We can import Button from Library/dist/Button but Next.JS still screams about code its not even using.
The next step on this adventure, and currently the last, was to use Webpacks splitChunks functionality, alongside the entry/output changes.
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// npm package names are URL-safe, but some servers don't like # symbols
return `npm.${packageName.replace('#', '')}`;
},
},
},
},
},
This has also not worked, though I am not 100% sure its even firing correctly, as I see no npm.packageName in my dist folder. There are now a bunch of 4531.js (3-4 numbers followed by js), but opening these, contained within the webpack generated code, are some classNames I have written, or the string that has been generated for my scss-modules.
What I am Going To Try Next
ALL RESULTS WILL BE POSTED ON THREAD
Making A Dummy Test Library
Making a library of three simple components (Red, Blue, Green) and trying to split them out. One will contain window, and using npm pack, we will keep making changes till something sticks in Next.JS. I don't necessarily think this will help, but may improve understanding of how everything is working.
Possible Solutions
Lerna + MicroLibraries
Funnily enough I looked at this when I first started on the library, realised it was a dragon I didn't need to tackle and ran away. The solution here, would be to separate out the categories into their own self contained npm packages. These would then be contained in a lerna enviroment. This could also be done without a tool like lerna, but we do not want to install part of the components library but all of it. I still feel like this route is over complicated, unnecessary and will cause more things to maintain in the long run. It is also going to require a rethink of structure and a rewrite of some sections i.e storybook, the docker image that deploys the storybook
Use Rollup or insert bundler name here
Again, this solution has a funny anecdote to go along with it. Lots of JS developers do not understand some of the fundamental tools they use. That isn't to say they are bad developers, but CLI tools like create-react-app generate a lot of the required project boilerplate, meaning the developer can focus on the functionality of their application. This was the case for my colleague and I, so we decided that it made sense to start from scratch. Webpack was the bundler I chose (and thank god for all those webpack 5 upgrades) but maybe this was the wrong decision and I should have used rollup?
Don't use Next.js
It is possible that this is an issue of Next.JS and that in reality Next.JS is the problem. I think that is a bad way to look at things however. Next.JS is a very cool framework and other than the problem being described here, has been wonderful to use. Our existing deployed application stacks are; Webpack, pug and express. Maybe deciding to use a framework is a bad move and we need to rewrite the application currently being developed in next. I do recall seeing that SSR errors could arise from react component life cycle methods/useEffect, so perhaps that has been the real culprit this whole time.
Extra
The library uses pnpm as its package manager.
Library Dependencies
"dependencies": {
"#fortawesome/fontawesome-pro": "^5.15.4",
"#fortawesome/fontawesome-svg-core": "^1.2.36",
"#fortawesome/free-regular-svg-icons": "^5.15.4",
"#fortawesome/free-solid-svg-icons": "^5.15.4",
"#fortawesome/pro-regular-svg-icons": "^5.15.4",
"#fortawesome/react-fontawesome": "^0.1.16",
"classname": "^0.0.0",
"classnames": "^2.3.1",
"crypto-js": "^4.1.1",
"date-fns": "^2.28.0",
"formik": "^2.2.9",
"html-react-parser": "^1.4.5",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"nanoid": "^3.2.0",
"react-currency-input-field": "^3.6.4",
"react-datepicker": "^4.6.0",
"react-day-picker": "^7.4.10",
"react-modal": "^3.14.4",
"react-onclickoutside": "^6.12.1",
"react-router-dom": "^6.2.1",
"react-select-search": "^3.0.9",
"react-slider": "^1.3.1",
"react-tiny-popover": "^7.0.1",
"react-toastify": "^8.1.0",
"react-trix": "^0.9.0",
"trix": "1.3.1",
"yup": "^0.32.11"
},
"devDependencies": {
"postcss-preset-env": "^7.4.2",
"#babel/core": "^7.16.12",
"#babel/preset-env": "^7.16.11",
"#babel/preset-react": "^7.16.7",
"#babel/preset-typescript": "^7.16.7",
"#dr.pogodin/babel-plugin-css-modules-transform": "^1.10.0",
"#storybook/addon-actions": "^6.4.14",
"#storybook/addon-docs": "^6.4.14",
"#storybook/addon-essentials": "^6.4.14",
"#storybook/addon-jest": "^6.4.14",
"#storybook/addon-links": "^6.4.14",
"#storybook/addons": "^6.4.14",
"#storybook/builder-webpack5": "^6.4.14",
"#storybook/manager-webpack5": "^6.4.14",
"#storybook/react": "^6.4.14",
"#storybook/theming": "^6.4.14",
"#svgr/webpack": "^6.2.0",
"#testing-library/react": "^12.1.2",
"#types/enzyme": "^3.10.11",
"#types/enzyme-adapter-react-16": "^1.0.6",
"#types/jest": "^27.4.0",
"#types/react": "^17.0.38",
"#types/react-datepicker": "^4.3.4",
"#types/react-dom": "^17.0.11",
"#types/react-slider": "^1.3.1",
"#types/yup": "^0.29.13",
"#typescript-eslint/eslint-plugin": "^5.10.1",
"#typescript-eslint/parser": "^5.10.1",
"#vgrid/sass-inline-svg": "^1.0.1",
"#wojtekmaj/enzyme-adapter-react-17": "^0.6.6",
"audit-ci": "^5.1.2",
"babel-loader": "^8.2.3",
"babel-plugin-inline-react-svg": "^2.0.1",
"babel-plugin-react-docgen": "^4.2.1",
"babel-plugin-react-remove-properties": "^0.3.0",
"clean-css-cli": "^5.5.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.1",
"css-loader": "^6.5.1",
"css-modules-typescript-loader": "^4.0.1",
"dependency-cruiser": "^11.3.0",
"enzyme": "^3.11.0",
"eslint": "^8.7.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-css-modules": "^2.11.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.0.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-sonarjs": "^0.11.0",
"eslint-webpack-plugin": "^3.1.1",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.4.7",
"jest-environment-enzyme": "^7.1.2",
"jest-environment-jsdom": "^27.4.6",
"jest-enzyme": "^7.1.2",
"jest-fetch-mock": "^3.0.3",
"jest-sonar-reporter": "^2.0.0",
"jest-svg-transformer": "^1.0.0",
"lint-staged": "^12.3.1",
"mini-css-extract-plugin": "^2.5.3",
"narn": "^2.1.0",
"node-notifier": "^10.0.0",
"np": "^7.6.0",
"postcss": "^8.4.5",
"postcss-loader": "^6.2.1",
"precss": "^4.0.0",
"prettier": "^2.5.1",
"prettier-eslint": "^13.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-is": "^17.0.2",
"sass": "^1.49.0",
"sass-loader": "^12.4.0",
"sass-true": "^6.0.1",
"sonarqube-scanner": "^2.8.1",
"storybook-formik": "^2.2.0",
"style-loader": "^3.3.1",
"ts-jest": "^27.1.3",
"ts-loader": "^9.2.6",
"ts-prune": "^0.10.3",
"typescript": "^4.5.5",
"typescript-plugin-css-modules": "^3.4.0",
"url-loader": "^4.1.1",
"webpack": "^5.67.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.3",
"webpack-node-externals": "^3.0.0"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
},
Thanks for reading and any suggestions would be great.
Update 1
First of all here is the webpack config I forgot to include, minus all the entry points.
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const inliner = require('#vgrid/sass-inline-svg');
const ESLintPlugin = require('eslint-webpack-plugin');
const pkg = require('./package.json');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// Note: Please add comments to new entry point category additions
entry: {
// Entry Points //
//// Main Entry Point ////
main: './src/index.ts',
//// Category Entry Points ////
Buttons: './src/components/Buttons/index.ts',
...
},
// context: path.resolve(__dirname),
resolve: {
modules: [__dirname, 'node_modules'],
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.scss', '.css'],
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js',
library: pkg.name,
libraryTarget: 'umd',
umdNamedDefine: true,
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
minChunks: 1,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// npm package names are URL-safe, but some servers don't like # symbols
return `npm.${packageName.replace('#', '')}`;
},
},
},
},
},
devtool: 'source-map',
module: {
rules: [
// ! This rule generates the ability to use S/CSS Modules but kills global css
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-modules-typescript-loader' },
{
loader: 'css-loader', //2
options: {
modules: {
localIdentName: '[local]_[hash:base64:5]',
},
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
extract: true,
modules: true,
use: ['sass'],
},
},
},
'sass-loader',
],
include: /\.module\.css$/,
},
// ! This allows for global css alongside the module rule. Also generates the d.ts files for s/css modules (Haven't figured out why).
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-modules-typescript-loader' },
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
extract: true,
use: ['sass'],
},
},
},
'sass-loader',
],
exclude: /\.module\.css$/,
},
{
test: /\.(ts|tsx)$/,
loader: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
// {
// test: /\.(js|jsx|ts|tsx)$/,
// exclude: /node_modules/,
// use: {
// loader: 'eslint-webpack-plugin',
// },
// },
{
test: /\.(png|jpg|jpeg|woff|woff2|eot|ttf)$/,
type: 'asset/resource',
},
{
test: /\.svg$/,
use: ['#svgr/webpack', 'url-loader'],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [{ from: './src/scss/**/*.scss', to: './scss/' }],
}),
new MiniCssExtractPlugin(),
new ESLintPlugin(),
],
externals: [nodeExternals()],
};
Extract the CSS!!!
An answer suggested it was the CSS modules being injected into the HTML that was the issue and I needed to extract. I updated the PostCSS rules in my webpack, to have extract: true and modules: true before recognising the problem. I am extracting all the css with webpack using the MiniCSSExtractPlugin. Due to Content-Security-Policy style rules on the webapps my company develops, the injection of styles into the HTML via tools like Style-Loader breaks everything. There are also very good argument against using tools like style-loader beyond a development environment.
I did more research into webpack extraction and saw people recommending different tools that played better with SSR. I have seen recommendations for MiniTextExtractPlugin (which was deprecated in favour of MiniCSSExtractPlugin), NullLoader (which I believe solves a completely different problem to the one I am facing), CSSLoader/Locales (which I can't find documentation for in the css-loader docs) and a few others; ObjectLoader, as well as style-loader, iso-style-loader etc. During my research into this, I recognised I was in a dead end. Perhaps MiniCSSExtractPlugin works poorly in the webpack of an application utilising SSR, but to quote an old video, "this is a library". Its built, packaged and published long before we install and utilise it in our application.
Next JS next.config.js next-transpile-modules
I updated the Next.JS config of my application based on this and a few other posts.
https://github.com/vercel/next.js/issues/10975#issuecomment-605528116
This is now my next.js config
const withTM = require('next-transpile-modules')(['#company/package']); // pass the modules you would like to see transpiled
module.exports = withTM({
webpack: (config, { isServer }) => {
// Fixes npm packages that depend on `fs` module
if (!isServer) {
config.resolve.fallback = {
fs: false,
};
}
return config;
},
});
This also did not solve the problem.
Stop SCSS being bundled with the Library
The library uses CopyWebpackPlugin to copy all the scss into a directory within the build. This allows us to expose mixins, variables, common global classnames etc. In an attempt to debug the webpack, I turned this off. This had no effect but I will document it anyway.
Update 1 Conclusion
I am currently replacing the bundler with rollup just to test whether or not it has any effect.
Update 2 Electric Boogaloo
So rollup was a failure, didn't solve any problems but did bring some issues to light.
Due to the nature of the problem, I decided to just dynamically load anything from the library that was needed, and to extract the loader from the library so I could use it for the dynamic loading.
If I manage to solve this problem in the way I intended, I will make another update. However I believe that this is just another issue with Next to add to the list.
I have encountered this same issue some months ago when creating my react library. I tried changing from webpack to rollup but that wasn't directly the issue. So I inspected the bundled code and figured out that the problem was the way both bundlers handled the css (I used css modules for my components). They use "window" and "document" for appending dynamically the css in the document. So I searched a lot and managed to configure the bundlers to extract the css the proper way for server side rendering. Here is my rollup.config.js in case you need it:
import commonjs from "#rollup/plugin-commonjs";
import resolve from "#rollup/plugin-node-resolve";
import typescript from "#rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import postcss from "rollup-plugin-postcss";
import { terser } from "rollup-plugin-terser";
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(),
peerDepsExternal(),
typescript({
tsconfig: "./tsconfig.json",
include: [
"src/**/*"
],
exclude: [
"node_modules",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.stories.tsx",
],
}),
postcss({
extract: true,
modules: true,
use: ["sass"],
}),
terser(),
],
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()],
},
];
I'm quite new to the front end - trying to implement client side on React. After adding "react-native" dependency and running webpack I'm getting the following error:
ERROR in ./node_modules/react-native/index.js 13:7
Module parse failed: Unexpected token (13:7)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| 'use strict';
|
> import typeof AccessibilityInfo from './Libraries/Components/AccessibilityInfo/AccessibilityInfo';
| import typeof ActivityIndicator from './Libraries/Components/ActivityIndicator/ActivityIndicator';
| import typeof Button from './Libraries/Components/Button';
# ./node_modules/#react-navigation/native/lib/module/useBackButton.js 2:0-43 5:25-36
# ./node_modules/#react-navigation/native/lib/module/index.js
# ./src/main/js/app.js
I have the following dependencies in package.json:
"dependencies": {
"#react-native-community/masked-view": "^0.1.7",
"#react-navigation/native": "^5.1.4",
"#react-navigation/stack": "^5.2.9",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-native": "^0.62.1",
"react-native-gesture-handler": "^1.6.1",
"react-native-reanimated": "^1.7.1",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.4.0",
"rest": "^2.0.0"
},
"scripts": {
"watch": "webpack --watch -d"
},
"devDependencies": {
"#babel/core": "^7.9.0",
"#babel/preset-env": "^7.9.0",
"#babel/preset-react": "^7.9.4",
"babel-loader": "^8.1.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
}
I assume that "typeof" operator is not recognized, but why?
I just spent a few hours dealing with this exact issue.
First, you can try adding "#babel/preset-flow" to your .babelrc file (after installing). That was recommended here, but didn't actually work for me.
What worked for me was just adding externals: { "react-native": true }, to my webpack.config.js file. My config file ended up looking like this:
const path = require("path")
module.exports = {
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
externals: {
"react-native": true,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ["babel-loader"],
},
// ...
],
},
}
My understanding is that react-native is a dependency, so #babel/preset-flow didn't actually get triggered to help interpret the flow formatting in those files - it may only handle files in the main "entry" location of your webpack.config.js file.
Hopefully this helps someone out there. Feel free to comment on this answer if I'm a bit off-base and I'll update this :)
Do you try to run React Native in the browser? If yes, I recommend using React Native for Web.
react-native can not run in the browser. You need to add an alias from react-native to react-native-web in webpack.config.js, so this package is used instead.
Following the React Native for Web documentation you need to make this modification to your webpack config:
// webpack.config.js
module.exports = {
// ...the rest of your config
resolve: {
alias: {
'react-native$': 'react-native-web'
}
}
}
Started a new project (copied from an existing) using angularjs 1.6.9. The previous project used bootstrap v 3.3.7. When I ran the npm install, it installed bootstrap 4.0.0. It has a dependency on JQuery and Tether, no big deal. It wouldn't npm install JQuery, but was able to get past that (turned off anti-virus/internet security). I have an index.html page with my navigation directive that contains a Bootstrap <nav> tag. When I try to view the page in the browser, the css is messed up and is not rendering as I expect the <nav> bar to render.
Naturally, I think I have something wrong with my css link or the js files aren't included, but they are as I can see the bootstrap css when inspecting the element:
This is my package.json file:
{
"name": "New site",
"version": "1.0.0",
"description": "A new site",
"main": "app.js",
"author": {
"name": "user1",
"email": ""
},
"dependencies": {
"angular": "^1.6.9",
"angular-jwt": "^0.1.9",
"angular-route": "^1.6.9",
"async": "^2.6.0",
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.18.2",
"bootstrap": "^4.0.0",
"dateformat": "^3.0.3",
"express": "^4.16.2",
"jquery": "^3.3.1",
"jsonwebtoken": "^8.1.1",
"mysql": "^2.15.0",
"popper.js": "^1.12.9",
"tether": "^1.4.3"
},
"devDependencies": {
"browser-sync": "^2.23.6",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^4.1.0",
"gulp-cache": "^1.0.2",
"gulp-concat": "^2.6.1",
"gulp-imagemin": "^4.1.0",
"gulp-jscs": "^4.1.0",
"gulp-jshint": "^2.1.0",
"gulp-notify": "^3.2.0",
"gulp-plumber": "^1.2.0",
"gulp-rename": "^1.2.2",
"gulp-uglify": "^3.0.0",
"jscs": "^3.0.7",
"jshint": "^2.9.5",
"jshint-stylish": "^2.2.1",
"nodemon": "^1.15.0"
}
}
I am bundling the css and js files from the node_modules folders into a 3rd-party file using these gulp tasks:
gulp.task('3rd-party-scripts', function () {
return gulp.src(['node_modules/jquery/dist/jquery.js',
'node_modules/tether/dist/js/tether.js',
'node_modules/bootstrap/dist/js/bootstrap.js',
'node_modules/angular/angular.js',
'node_modules/angular-route/angular-route.js',
'node_modules/angular-jwt/dist/angular-jwt.js'])
.pipe(plumber({
errorHandler: function (error) {
console.log(error.message);
this.emit('end');
}
}))
.pipe(concat('3rd-party.min.js'))
.pipe(gulp.dest('public/dist/scripts/'))
.pipe(browserSync.reload({ stream: true }))
});
gulp.task('3rd-party-styles', function () {
return gulp.src(['node_modules/bootstrap/dist/css/bootstrap.css'])
.pipe(plumber({
errorHandler: function (error) {
console.log(error.message);
this.emit('end');
}
}))
.pipe(concat('3rd-party-styles.min.css'))
.pipe(gulp.dest('public/dist/css/'))
.pipe(browserSync.reload({ stream: true }))
});
I've loaded the full files to debug even though the 3rd party files are named min.js and min.cs. They are loaded with no console errors. No 404 errors. And no bootstrap formatting!
Here is the index.html:
It feels like the bootstrap.js is not executed. I played around with the order in the gulpfile when adding to the 3rd-party file. In my digging, I thought I might have to inject bootstrap into my angular module. Another try was calling angular.bootstrap(document, ['ttab']); but this caused an error that bootstrap was already loaded, so I felt comfortable that it is doing what it should.
Bootstrap 4 is totally incompatible with Bootstrap 3 as it's almost a complete rewrite.
Solution:
Either load the css (as well as the corresponding js) files for Bootstrap 3 OR migrate to Bootstrap 4.
If you want to migrate, you'll need to manually adjust/change a lot of stuff.
Reference: http://getbootstrap.com/docs/4.0/migration/
If you want to continue using Bootstrap 3, you can use these links:
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js
Also, Bootstrap 4.0.0 does not have Tether as a dependency. It uses popper.js instead.
I'm trying to upgrade a giant client-side app from Angular 1.x to Angular 2, and I've hit a really annoying roadblock. I've recreated the issue with a dummy, light-weight project (files pasted below), but let me just explain the problem first.
Basically, when my tsconfig.json specifies module as commonjs, I get the following error in my chrome dev console:
app.module.ts:1Uncaught ReferenceError: require is not defined
When I switch the module to system, I get the following error:
Uncaught TypeError: Invalid System.register call. Anonymous System.register calls can only be made by modules loaded by SystemJS.import and not via script tags.
And when I switch module to umd, everything works fine. But given that angular themselves suggest using commonjs, I'm concerned that umd is not the right answer. However, if I'm wrong about that and umd is perfectly fine, I'd love to hear a good explanation as to why.
Here's my code to reproduce my issue:
tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "system",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
}
,
"exclude": [
"node_modules"
]
}
typings.json:
{
"globalDependencies": {
"angular": "registry:dt/angular#1.5.0+20160922195358",
"core-js": "registry:dt/core-js#0.0.0+20160725163759",
"jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
"jquery": "registry:dt/jquery#1.10.0+20160929162922",
"node": "registry:dt/node#6.0.0+20160909174046"
}
}
systemjs.config.js :
(function (global) {
System.config({
paths: {
// paths serve as alias
'npm:': 'node_modules/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'app',
// angular bundles
'#angular/core': 'npm:#angular/core/bundles/core.umd.js',
'#angular/common': 'npm:#angular/common/bundles/common.umd.js',
'#angular/compiler': 'npm:#angular/compiler/bundles/compiler.umd.js',
'#angular/platform-browser': 'npm:#angular/platform-browser/bundles/platform-browser.umd.js',
'#angular/platform-browser-dynamic': 'npm:#angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'#angular/ ': 'npm:#angular/http/bundles/http.umd.js',
'#angular/router': 'npm:#angular/router/bundles/router.umd.js',
'#angular/forms': 'npm:#angular/forms/bundles/forms.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
},
'angular-in-memory-web-api': {
main: './index.js',
defaultExtension: 'js'
}
}
});
})(this);
package.json :
{
"name": "mattsApp",
"version": "0.0.1",
"dependencies": {
"angular": "^1.5.8",
"#angular/common": "~2.0.2",
"#angular/compiler": "~2.0.2",
"#angular/core": "~2.0.2",
"#angular/forms": "~2.0.2",
"#angular/http": "~2.0.2",
"#angular/platform-browser": "~2.0.2",
"#angular/platform-browser-dynamic": "~2.0.2",
"#angular/router": "~3.0.2",
"#angular/upgrade": "~2.0.2",
"angular-in-memory-web-api": "~0.1.5",
"bootstrap": "^3.3.7",
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.8",
"rxjs": "5.0.0-beta.12",
"systemjs": "0.19.39",
"zone.js": "^0.6.25"
},
"devDependencies": {
"concurrently": "^3.0.0",
"lite-server": "^2.2.2",
"typescript": "^2.0.3",
"typings":"^1.4.0"
},
"scripts": {
"start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
"lite": "lite-server",
"postinstall": "typings install",
"tsc": "tsc",
"tsc:w": "tsc -w",
"typings": "typings"
}
}
app.js :
angular.module('app', []);
app.module.ts :
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { upgradeAdapter } from './upgrade_adapter';
#NgModule({
imports: [ BrowserModule ]
})
export class AppModule { }
upgradeAdapter.bootstrap(document.body, ['app'], {strictDi: true});
appCtrl.ts :
angular.module('app')
.controller('appCtrl', ['$scope', function($scope) {
$scope.hello = "howdy worldy";
}]);
upgrade_adapter.ts :
import { UpgradeAdapter } from '#angular/upgrade';
import {AppModule} from "./app.module";
export const upgradeAdapter = new UpgradeAdapter(AppModule);
What am I missing?
you need instantiate the UpgradeAdapter with a non null parametter. In this way you need pass a forwardRef instance, something like this:
const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
finally you need import the forwardRef
import {NgModule, forwardRef} from '#angular/core';
First, thanks Andres. I need to read up a bit about the forwardRef to understand what that does. But it turns out that the answer in my particular case was something different.
I didn't post my index.html here (simple oversight), but the problem was that I wasn't loading my modules with the System.import('app'). Embarrassing error, I have to admit. So the answer is to add that line.
I should note that this led to a different error which I solved, but I'll point it out here in case others have a similar issue. Since this is a hybrid angular 1 and 2 app, I have typescript files that are sometimes used by angular 1 controllers / directives / etc, and also by angular 2 components. I had changed those typescript files to use export so I could import them into my angular 2 components. This led me to also change my /// in the angular 1 files to use import. Unfortunately, this gave me an "undefinedModule" error. The solution (in my case) is not to use export on typescript files unless they are ONLY used with the angular 2 components. Meaning, in some angular 2 components, I'm actually using the /// and not the import.
Just thought other people might find that useful.
Good evening,
I'm working on a project written in NodeJS. It uses an express server and many other dependencies like angular, googlemaps, tika, karma, etc.
A few weeks ago I've got the order to make the webpage (localhost) faster by implementing webpack. It bundles all stylesheets and scripts into one file.
After a lot of work to solve all dependencies I've got the app running even though many errors couldn't be found with google. e.g. "java" is a sub dependency which calls "node-gyp rebuild" which is doomed to failure on windows because node-gyp need python and c++ compiler. After installing Windows SDK, VS 2015, etc. I could fix this problem switching to the 32 bit version of node (instead of 64 bit).
After repairing all dependencies I switched to the 64 bit version to solve another problem. The webpack config is due to the amount of dependencies (which needs "alias" entries) quite long.
Two weeks ago I started to implement a developer mode so you can change code and webpack-dev-server automatically rebuilds the bundle file. Therefore I followed a tutorial from SurviveJS.
I've got it easily running after the tutorial. But for a presentation I wanted to clone the Github repository and install all dependencies a second time so I can be sure that the project works. But this time a new error appeared when I was running webpack or webpack-dev-server which couldn't be solved with my previous tricks:
Path_to_project\demo\n2o>..\node_modules\.bin\webpack --config webpack.config.js
Error: No module factory available for dependency type: ModuleDependency
at Compilation.addModuleDependencies (Path_to_project\demo\node_modules\webpack\lib\Compilation.js:181:20)
at Compilation.processModuleDependencies (Path_to_project\demo\node_modules\webpack\lib\Compilation.js:170:7)
at Compilation.moduleReady (Path_to_project\demo\node_modules\webpack\lib\Compilation.js:412:9)
at Compilation.<anonymous> (Path_to_project\demo\node_modules\webpack\lib\Compilation.js:408:16)
at Path_to_project\demo\node_modules\webpack\lib\Compilation.js:123:4
at Array.forEach (native)
at callback (Path_to_project\demo\node_modules\webpack\lib\Compilation.js:122:12)
at Compilation.<anonymous> (Path_to_project\demo\node_modules\webpack\lib\Compilation.js:140:10)
at DependenciesBlock.<anonymous> (Path_to_project\demo\node_modules\webpack\lib\NormalModule.js:115:10)
at DependenciesBlock.onModuleBuild (Path_to_project\demo\node_modules\webpack\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
at nextLoader (Path_to_project\demo\node_modules\webpack\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
at Path_to_project\demo\node_modules\webpack\node_modules\webpack-core\lib\NormalModuleMixin.js:259:5
at Storage.finished (Path_to_project\demo\node_modules\webpack\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:38:16)
at Path_to_project\demo\node_modules\webpack\node_modules\enhanced-resolve\node_modules\graceful-fs\graceful-fs.js:76:16
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:380:3)
When I remove all plugins from the webpack config the error disappears but obviously running the app fails because the angular-webpack-plugin is missing and most of the dependencies are not loaded by webpack. What could be the reason for this failure? I tried different versions of webpack (1.12.2 worked out for me after discovering this fix in an issue on Github) and webpack-dev-server.
I would appreciate your help,
Thomas Kellermeier
Files (webpack.config.js, package.json):
webpack.config.js (webpack)
The settings for webpack-dev-server are at the end of the config
var path = require('path');
var AngularPlugin = require('angular-webpack-plugin');
var webpack = require("webpack");
require("css-selector-tokenizer");
var merge = require('webpack-merge');
const TARGET = process.env.npm_lifecycle_event;
common = {
entry: 'n2o',
output: {
path: 'public/app',
filename: 'n2o.min.js',
publicPath: 'app/'
},
externals: {
"jquery": "jQuery"
},
resolve: {
root: [
// Directories containing modules
path.resolve('public'),
path.resolve('public', 'assets/libs'),
path.resolve('public', 'components'),
path.resolve('public', 'components/settings'),
path.resolve('public', 'components/splitapp'),
path.resolve('public', 'components/register'),
path.resolve('public', 'shared/postContent'),
path.resolve('public', 'shared'),
// Needed for webpack-dev-server modules
path.resolve('public', '../../node_modules')
],
modulesDirectories:['public/assets/libs'],
'dev-tool':'eval',
alias: {
// Replace module names with belonging file names
n2o$: 'app.module',
ngFileUpload$: 'ng-file-upload',
toaster$: 'angularjs-toaster',
ngCookies$: 'angular-cookies',
'ui.router$': __dirname+'/public/assets/libs/angular-ui-router/release/angular-ui-router.js',
'jQuery$': __dirname+'/public/assets/libs/jquery/dist/jquery.js',
registration$: 'register',
ngAnimate$: 'angular-animate',
'mm.foundation$': __dirname+'/public/assets/libs/angular-foundation/mm-foundation.js',
vcRecaptcha$:'angular-recaptcha',
filters$: 'postContentController',
'cfp.hotkeys$':'angular-hotkeys',
'angulartics.google.analytics$':__dirname+'/public/assets/libs/angulartics/dist/angulartics-ga.min.js',
splitapp$:'splitappController',
register$:'registerController',
'ngTouch$':'angular-touch',
'ui-rangeSlider$':__dirname+'/public/assets/libs/angular-rangeslider/angular.rangeSlider.js',
ngSanitize:'angular-sanitize',
settingsModule: 'settingsMasterController',
ngTagsInput$:'ng-tags-input',
'chart.js$':'angular-chart.js',
// Webpack-dev-server dependency
'inherits':__dirname+'\\..\\node_modules\\inherits',
n2o: path.resolve('public'),
ngRoute$: 'angular-route'
}
},
module: {
loaders: [{
// Copy png, jpg, jpeg, gif, svg, woff, woff2, ttf, eot files to output
// Rename the file using the asset hash
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)/,
loader: 'file'
}, {
// Allow loading html through js
test: /\.html$/,
loader: 'raw'
}, {
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.json$/,
loader: "json-loader"
}]
},
plugins: [
// Make angular variable available, take angular.module() as requires and
// resolve modules using angular conventions.
new AngularPlugin()
]
};
// For using the development mode the webpack-dev-server has to run with
// following config.
if(TARGET === 'start-dev-server' || !TARGET) {
common.output.publicPath = 'http://localhost:9090/app/'
module.exports = merge(common, {
devtool: 'eval-source-map',
debug: true,
devServer: {
historyApiFallback: true,
hot: true,
inline: true,
quiet: true,
host: process.env.HOST,
port: '9090'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
} else {
module.exports = merge(common, {
plugins: [
new webpack.optimize.UglifyJsPlugin({
minimize: true,
sourceMap: false,
mangle: false,
compress: {
warnings: false
}
})
]
})
}
package.json (dependencies)
[...]
"scripts": {
"compile-production": "webpack --config webpack.config.js",
"start-dev-server": "webpack-dev-server --config webpack.config.js",
"start-dev-mode": "start node n2o/app.js & npm run start-dev-server",
"install-webpack": "npm install webpack#1.12.2 -g & npm install webpack-dev-server#1.14.0 -g",
[...]
},
"dependencies": {
"MD5": "^1.2.1",
"adm-zip": "^0.4.7",
"aws-sdk": "^2.1.34",
"body-parser": "^1.9.2",
"bower": "^1.3.1",
"compression": "^1.5.0",
"cookie-parser": "^1.3.3",
"elasticsearch": "^9.0.0",
"express": "^4.10.1",
"express-session": "^1.10.2",
"facebook-node-sdk": "^0.2.0",
"get-down": "^0.5.0",
"gnip": "~0.2.1",
"googlemaps": "^0.1.20",
"hdb": "^0.4.1",
"html-pdf": "^1.2.0",
"json2csv": "^2.2.1",
"limiter": "^1.0.5",
"lodash": "^3.7.0",
"mocha": "~2.2.1",
"moment": "^2.9.0",
"morgan": "^1.4.1",
"multer": "~0.1.8",
"node-ipc": "^1.1.13",
"node-tika": "^0.0.1",
"oauth": "^0.9.12",
"oauth-request": "0.0.1",
"passport": "^0.2.2",
"passport-local": "~1.0.0",
"phantom": "*",
"q": "^1.1.2",
"request": "^2.53.0",
"response-time": "^2.3.1",
"sanitize-html": "^1.6.1",
"string-format": "^0.5.0",
"stylus": "0.42.3",
"swig": "^1.4.2",
"tika": "^1.3.0",
"tough-cookie": "^0.12.1",
"twin-bcrypt": "^2.1.1",
"twit": "^1.1.20",
"underscore": "~1.8.2",
"unzip": "^0.1.11",
"webdriver-manager": "^7.0.1",
"winston": "^0.9.0"
},
"devDependencies": {
"angular-webpack-plugin": "0.0.3",
"babel-loader": "^5.3.2",
"bower-webpack-plugin": "^0.1.8",
"chai": "^1.10.0",
"chai-as-promised": "^4.1.1",
"css-loader": "^0.21.0",
"css-selector-tokenizer": "^0.5.4",
"exports-loader": "^0.6.2",
"file-loader": "^0.8.4",
"jasmine-node": "~1.14.5",
"json-loader": "^0.5.2",
"karma": "^0.12.16",
"karma-chrome-launcher": "^0.1.4",
"karma-coverage": "~0.3.1",
"karma-firefox-launcher": "~0.1.4",
"karma-jasmine": "^0.1.5",
"ng-annotate-webpack-plugin": "^0.1.2",
"protractor": "^1.7.0",
"raw-loader": "^0.5.1",
"should": "^5.0.0",
"style-loader": "^0.12.3",
"supertest": "^0.15.0",
"webpack": "^1.12.2",
"webpack-dev-server": "^1.14.0",
"webpack-merge": "^0.3.2"
}
[...]