React Native Web implement SSR - react-native-web

https://necolas.github.io/react-native-web/docs/rendering/
After reading the SSR example from the document, I still don't know how to implement SSR
And I don't want to apply SSR with other framework like NextJS
Can anyone show me an example or give me some advice

I'm posting this, not as a direct answer to the original question, because it's targeted directly SSR with NextJS, and the OP needed SSR independently from frameworks like NextJS. However, understanding it with NextJS can get anyone closer with things, because they key relies in Webpack config that NextJS also use as SSR in its encapsulation config.
First thing to know is that, once a Package has been written for React Native, it need to be transpiled first to be able to be used in Web, with webpack config.externals.
let modulesToTranspile = [
'react-native',
'react-native-dotenv',
'react-native-linear-gradient',
'react-native-media-query',
'react-native-paper',
'react-native-view-more-text',
// 'react-native-vector-icons',
];
Then you need to alias some react-native packages to react-native-web equivalent to let package use web version of modules like:
config.resolve.alias = {
...(config.resolve.alias || {}),
// Transform all direct `react-native` imports to `react-native-web`
'react-native$': 'react-native-web',
'react-native-linear-gradient': 'react-native-web-linear-gradient',
};
At this point, you almost get the essential. The rest is normal Webpack config for the normal Application. Also, it needs some additional config in native config file too. I will post all configs content.
For NextJS: next.config.js :
const path = require('path');
let modulesToTranspile = [
'react-native',
'react-native-dotenv',
'react-native-linear-gradient',
'react-native-media-query',
'react-native-paper',
'react-native-view-more-text',
// 'react-native-vector-icons',
];
// console.log('modules to transpile', modulesToTranspile);
// import ntm = from 'next-transpile-modules';
// const withTM = ntm(modulesToTranspile);
// logic below for externals has been extracted from 'next-transpile-modules'
// we won't use this modules as they don't allow package without 'main' field...
// https://github.com/martpie/next-transpile-modules/issues/170
const getPackageRootDirectory = m =>
path.resolve(path.join(__dirname, 'node_modules', m));
const modulesPaths = modulesToTranspile.map(getPackageRootDirectory);
const hasInclude = (context, request) => {
return modulesPaths.some(mod => {
// If we the code requires/import an absolute path
if (!request.startsWith('.')) {
try {
const moduleDirectory = getPackageRootDirectory(request);
if (!moduleDirectory) {
return false;
}
return moduleDirectory.includes(mod);
} catch (err) {
return false;
}
}
// Otherwise, for relative imports
return path.resolve(context, request).includes(mod);
});
};
const configuration = {
node: {
global: true,
},
env: {
ENV: process.env.NODE_ENV,
},
// optimizeFonts: false,
// target: 'serverless',
// bs-platform
// pageExtensions: ['jsx', 'js', 'bs.js'],
// options: { buildId, dev, isServer, defaultLoaders, webpack }
webpack: (config, options) => {
// config.experimental.forceSwcTransforms = true;
// console.log('fallback', config.resolve.fallback);
if (!options.isServer) {
// We shim fs for things like the blog slugs component
// where we need fs access in the server-side part
config.resolve.fallback.fs = false;
} else {
// SSR
// provide plugin
config.plugins.push(
new options.webpack.ProvidePlugin({
requestAnimationFrame: path.resolve(__dirname, './polyfills/raf.js'),
}),
);
}
// react-native-web
config.resolve.alias = {
...(config.resolve.alias || {}),
// Transform all direct `react-native` imports to `react-native-web`
'react-native$': 'react-native-web',
'react-native-linear-gradient': 'react-native-web-linear-gradient',
};
config.resolve.extensions = [
'.web.js',
'.web.ts',
'.web.tsx',
...config.resolve.extensions,
];
config.externals = config.externals.map(external => {
if (typeof external !== 'function') {
return external;
}
return async ({ context, request, getResolve }) => {
if (hasInclude(context, request)) {
return;
}
return external({ context, request, getResolve });
};
});
const babelLoaderConfiguration = {
test: /\.jsx?$/,
use: options.defaultLoaders.babel,
include: modulesPaths,
// exclude: /node_modules[/\\](?!react-native-vector-icons)/,
};
babelLoaderConfiguration.use.options = {
...babelLoaderConfiguration.use.options,
cacheDirectory: false,
// For Next JS transpile
presets: ['next/babel'],
plugins: [
['react-native-web', { commonjs: true }],
['#babel/plugin-proposal-class-properties'],
// ['#babel/plugin-proposal-object-rest-spread'],
],
};
config.module.rules.push(babelLoaderConfiguration);
return config;
},
};
// module.exports = withTM(config);
module.exports = configuration;
SSR will fail to build when missing some functions at server side. The most popular with React Native is requestAnimationFrame. I added it add a Webpack Plugin to mimic it. It can be an empty function or Polyfill:
The file 'polyfills/raf.js(I just put it assetImmediate`):
const polys = { requestAnimationFrame: setImmediate };
module.exports = polys.requestAnimationFrame;
The Babel config is necessary for the last part of it, couldn't work directly in next config. babel.config.js :
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [['module:react-native-dotenv'], 'react-native-reanimated/plugin'],
};
And finally, my list of packages in package.json:
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"android": "react-native run-android",
"android:dev": "adb reverse tcp:8081 tcp:8081 && react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint .",
"web": "webpack serve -d source-map --mode development --config \"./web/webpack.config.js\" --inline --color --hot",
"build:web": "webpack --mode production --config \"./web/webpack.config.js\" --hot",
"next:dev": "next",
"next:build": "next build",
"next:start": "next start",
"next:analyze": "ANALYZE=true next build"
},
"dependencies": {
"#material-ui/core": "^4.12.4",
"#react-native-async-storage/async-storage": "^1.17.3",
"#react-navigation/drawer": "^6.4.1",
"#react-navigation/native": "^6.0.10",
"#react-navigation/stack": "^6.2.1",
"#reduxjs/toolkit": "^1.8.1",
"axios": "^0.21.1",
"local-storage": "^2.0.0",
"lottie-ios": "^3.2.3",
"lottie-react-native": "^5.1.3",
"lottie-web": "^5.9.4",
"moment": "^2.29.1",
"next": "^12.1.6",
"nookies": "^2.5.2",
"numeral": "^2.0.6",
"raf": "^3.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-native": "0.68.1",
"react-native-dotenv": "^2.5.5",
"react-native-gesture-handler": "^2.4.2",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-linear-gradient": "^2.5.6",
"react-native-media-query": "^1.0.9",
"react-native-paper": "^4.12.1",
"react-native-progress": "^5.0.0",
"react-native-read-more-text": "^1.1.2",
"react-native-reanimated": "^2.8.0",
"react-native-safe-area-context": "^4.2.5",
"react-native-screens": "^3.13.1",
"react-native-share-menu": "^6.0.0",
"react-native-svg": "^12.3.0",
"react-native-svg-transformer": "^1.0.0",
"react-native-vector-icons": "^9.1.0",
"react-native-view-more-text": "^2.1.0",
"react-native-web": "^0.17.7",
"react-native-web-linear-gradient": "^1.1.2",
"react-redux": "^8.0.1"
},
"devDependencies": {
"#babel/plugin-proposal-class-properties": "^7.14.5",
"#next/bundle-analyzer": "^12.2.2",
"#react-native-community/eslint-config": "^2.0.0",
"#swc/cli": "^0.1.57",
"#swc/core": "^1.2.179",
"eslint": "^7.28.0",
"metro-react-native-babel-preset": "^0.66.0",
"url-loader": "^4.1.1",
"webpack": "^5.39.1",
"webpack-cli": "^4.7.2"
},
"jest": {
"preset": "react-native-web"
},
"sideEffects": false
}
NB: only React-Native packages used also in Web has to be transpiled. Some React-Native packages can be used ONLY in Native, so transpiling them for Web will add up unnecessary chunks of heavy codes in the Web, which is not good. React-Native-Web/React-Native is already more heavy for Web than normal packages made directly for Web.
TIPS to keep it cool with NextJS
Avoid writing conditional Platform.OS === 'web' on small components where you plan to use either a React-Native module or a Web module, which can cause all of them to load unnecessary Native-Only package on web codes. If size is not important, then you can ignore it. Add extension .web.js and .native.js at the end and separate the small codes. For example I write separate Functions and Components for : Storage.web.js, Storage.native.js, CustomLink.web.js, CustomLink.native.js, and hooks useCustomNavigation.web.js, useCustomNavigation.native.js, so that I call CustomLink in place of NextJS Link/router and React-Navigation Link/navigation.
I use react-native-media-query package as life saver for advanced media queries for all SSR/CSR Web and Native responsive display. The App can be restructured on big screen like normal Desktop Web, and be shrunk to Mobile View on the go, EXACTLY LIKE Material-UI on NextJS.

Related

Custom Node server causes Nextjs to fail to load 'No `pages` directory found'

I'm trying to bootstrap a new Nextjs app, and for integrating with Auth0 I need my localhost to be running HTTPS. I followed the guide here (https://medium.com/responsetap-engineering/nextjs-https-for-a-local-dev-server-98bb441eabd7), and generated a local certificate which is in more trusted certificate store.
To use HTTPS for localhost, you apparently need to create a custom server (this seems an odd oversight on the Nextjs side), so here's my custom server:
const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev, dir: __dirname });
const handle = app.getRequestHandler();
const httpsOptions = {
key: fs.readFileSync('./certificates/ReactDevCertificate.key'),
cert: fs.readFileSync('./certificates/ReactDevCertificate.cer'),
};
app.prepare().then(() => {
createServer(httpsOptions, (req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(3000, (err) => {
if (err) throw err;
console.log('> Server started on https://localhost:3000');
});
});
Now, previous without the custom server, the app loads fine. But, with the custom server, it fails to load:
➜ yarn run dev
yarn run v1.22.15
$ next dev ./server.js
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
Error: > No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?
at Object.findPagesDir (C:\Clients\ING\Framework\samples\fictionist-ui\node_modules\next\dist\lib\find-pages-dir.js:31:15)
at new DevServer (C:\Clients\ING\Framework\samples\fictionist-ui\node_modules\next\dist\server\dev\next-dev-server.js:110:44)
at NextServer.createServer (C:\Clients\ING\Framework\samples\fictionist-ui\node_modules\next\dist\server\next.js:102:20)
at C:\Clients\ING\Framework\samples\fictionist-ui\node_modules\next\dist\server\next.js:117:42
at async NextServer.prepare (C:\Clients\ING\Framework\samples\fictionist-ui\node_modules\next\dist\server\next.js:92:24)
at async C:\Clients\ING\Framework\samples\fictionist-ui\node_modules\next\dist\cli\next-dev.js:126:9
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
It feels like somewhere the app has navigated to a child folder, as there is code in the function find-pages-dir.js (https://github.com/vercel/next.js/blob/canary/packages/next/lib/find-pages-dir.ts#L22) that looks specifically to the parent directory for the pages folder.
For reference, here is my package.json:
{
"name": "fictionist-ui",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev ./server.js",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "12.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.4"
},
"devDependencies": {
"#types/node": "16.11.6",
"#types/react": "17.0.33",
"eslint": "7.32.0",
"eslint-config-next": "12.0.1",
"typescript": "4.4.4"
}
}
OS: Windows 11
NPM: 16.9.0
Yarn: 1.22.15
My mistake was that I didn't get the command right:
"scripts": {
"dev": "node server.js" // was "next dev ./server.js"
}

Adding Vuex-ORM to Laravel Nova tool

Can someone help me out with adding vuex-orm to a Laravel Nova Tool.
The base of a Laravel Nova tool has tool.js with the following content ('planning-tool' in name, and path may vary according to the name of your tool):
Nova.booting((Vue, router, store) => {
router.addRoutes([
{
name: 'planning-tool',
path: '/planning-tool',
component: require('./components/Tool'),
},
])
})
As you can see Laravel Nova already has a store present.
According to the Vuex-ORM docs (https://vuex-orm.org/guide/prologue/getting-started.html#register-models-to-vuex), I should get it started using:
import Vue from 'vue'
import Vuex from 'vuex'
import VuexORM from '#vuex-orm/core'
import User from '#/models/User'
Vue.use(Vuex)
// Create a new instance of Database.
const database = new VuexORM.Database()
// Register Models to Database.
database.register(User)
// Create Vuex Store and register database through Vuex ORM.
const store = new Vuex.Store({
plugins: [VuexORM.install(database)]
})
export default store
Since Laravel Nova already has a Vuex store I was trying to mix it up. But as soon as I add an import related to #vuex-orm/core I get the following error:
ERROR in ./node_modules/#vuex-orm/core/dist/vuex-orm.esm.js
Module parse failed: Unexpected token (1105:21)
You may need an appropriate loader to handle this file type.
| }
| if (typeof target === 'object' && target !== {}) {
| const cp = { ...target };
| Object.keys(cp).forEach((k) => (cp[k] = cloneDeep(cp[k])));
| return cp;
# ./resources/js/tool.js 1:0-37
# multi ./resources/js/tool.js ./resources/sass/tool.scss
My code (just so far) is:
import VuexORM from '#vuex-orm/core'
Nova.booting((Vue, router, store) => {
router.addRoutes([
{
name: 'planning-tool',
path: '/planning-tool',
component: require('./components/NewTool'),
},
])
// Create a new instance of Database.
const database = new VuexORM.Database()
// TODO ...
})
Does anybody have a suggestion? I know I can use normal Vuex store but vuex-orm adds so many nice features (which I'm used to) that I would take me a lot of time to work with normal objects as models by looping through them and loading additional relationships.
FURTHER AHEAD
To get ahead of myself, how should I register the vuex-orm database plugin since all I can find is creating a Vuex store with the (VuexORM) plugin directly passed along. I've read that a plugin is just function with the store as the only argument, so would something like this work? I've read that it does not always work when you use it like this.
const plugin = VuexORM.install(database)
plugin(store)
Any suggestions of what to try is welcome, I've been at it for quite a while now...
The problem was with webpack and/or Laravel mix 1. We solved it by adding babel dependencies and upgrading Laravel mix tot ^6.0.19.
Our package.json is:
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production"
},
"devDependencies": {
"#babel/core": "^7.14.3",
"#babel/plugin-transform-runtime": "^7.14.3",
"#babel/preset-env": "^7.14.4",
"#vuex-orm/core": "^0.36.4",
"babel-loader": "^8.2.2",
"cross-env": "^5.0.0",
"laravel-mix": "^6.0.19",
"vue": "^2.6.14",
"vue-loader": "^15.9.7",
"vuex": "^3.6.2"
},
"dependencies": {
"#babel/runtime": "^7.14.0"
}
}
Our .babelrc is:
{
"presets": [
[
"#babel/preset-env",
{
"useBuiltIns": false
}
]
],
"plugins": [
"#babel/transform-runtime"
]
}
Our tool.js is:
import VuexORM from '#vuex-orm/core'
import ExampleModel from './models/ExampleModel'
import Tool from './components/Tool'
Nova.booting((Vue, router, store) => {
router.addRoutes([
{
name: 'planning-tool',
path: '/planning-tool',
component: Tool,
},
])
// Create a new instance of Database.
const database = new VuexORM.Database()
database.register(ExampleModel)
const plugin = VuexORM.install(database)
plugin(store)
})
I hope this helps someone in the future.

How to configure Next.js with Antd / Less and Sass / CSS modules

I want to use Next.js with Sass and CSS modules but also want to use Ant Design and wanted to use the Less styles for smaller building size.
I'm able to enable either CSS modules or Less loader but not both at the same time. The examples from Next.js were not helping me complete that problem.
Edit: This answer is definitely outdated for current versions of next.js, check the other answers below.
After multiple hours of research I found now finally the right solution and wanted to share it:
.babelrc (no magic here)
{
"presets": ["next/babel"],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": true
}
]
]
}
next.config.js:
/* eslint-disable */
const withLess = require('#zeit/next-less');
const withSass = require('#zeit/next-sass');
const lessToJS = require('less-vars-to-js');
const fs = require('fs');
const path = require('path');
// Where your antd-custom.less file lives
const themeVariables = lessToJS(
fs.readFileSync(path.resolve(__dirname, './assets/antd-custom.less'), 'utf8')
);
module.exports = withSass({
cssModules: true,
...withLess({
lessLoaderOptions: {
javascriptEnabled: true,
modifyVars: themeVariables, // make your antd custom effective
importLoaders: 0
},
cssLoaderOptions: {
importLoaders: 3,
localIdentName: '[local]___[hash:base64:5]'
},
webpack: (config, { isServer }) => {
//Make Ant styles work with less
if (isServer) {
const antStyles = /antd\/.*?\/style.*?/;
const origExternals = [...config.externals];
config.externals = [
(context, request, callback) => {
if (request.match(antStyles)) return callback();
if (typeof origExternals[0] === 'function') {
origExternals[0](context, request, callback);
} else {
callback();
}
},
...(typeof origExternals[0] === 'function' ? [] : origExternals)
];
config.module.rules.unshift({
test: antStyles,
use: 'null-loader'
});
}
return config;
}
})
});
The final hint how to write the withSass withLess use and to put the cssModules: true in the outer object came from this comment here.
While I was already trying different combinations derived from the examples before:
next+ant+less
next+sass
For completion here the dependencies in my package.json:
...
"dependencies": {
"#zeit/next-less": "^1.0.1",
"#zeit/next-sass": "^1.0.1",
"antd": "^4.1.3",
"babel-plugin-import": "^1.13.0",
"less": "^3.11.1",
"less-vars-to-js": "^1.3.0",
"next": "^9.3.4",
"node-sass": "^4.13.1",
"null-loader": "^3.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"sass": "^1.26.3"
}
...
I hope this helps other people to find this solution faster. :)
#zeit/next-less is deprecated and disables Next's built in CSS support. It also uses a very old version of less and less-loader.
I created a package that injects Less support to Next.js by duplicating the SASS rules and setting them for Less files and less-loader. It works with webpack5 flag.
https://github.com/elado/next-with-less
https://www.npmjs.com/package/next-with-less
While the above answers may work for versions of NextJS lower than 11, they do not work for 11+. I've found excellent success with the following plugin...
https://github.com/SolidZORO/next-plugin-antd-less
I am using elado's package which is -
https://github.com/elado/next-with-less
you will need less and less-loader as dependencies.
after that create a global.less file on styles folder. so it's like ,root> style > global.less and paste this code
#import '~antd/lib/style/themes/default.less';
#import '~antd/dist/antd.less';
#primary-color: #ff9b18;
#border-radius-base: 20px;
and add below code in your next.config.js file which you will create on your root folder.
// next.config.js
const withLess = require("next-with-less");
module.exports = withLess({
lessLoaderOptions: {
/* ... */
},
});
To add Less to the Next.js is easy way.
Need to add 'next-with-less' library (also install less and less-loader) and 'next-compose-plugin'.
To your next.config.js add:
/** #type {import('next').NextConfig} */
const withPlugins = require('next-compose-plugins');
const withLess = require('next-with-less');
const plugins = [
[
withLess,
{
lessLoaderOptions: {},
},
],
];
module.exports = withPlugins(plugins, {
reactStrictMode: true,
swcMinify: true,
});
In our project, there were old scss and css files. They were not using the Next js guidline for CSS modules. So I had to override webpack.config.js. So that works fine. But when we moved the file to the monorepo shared package, babel was not transpiling them. I used the below things, but those did not work for SCSS modules without a .module extension.
next-transile-module.
experimental: { externalDir: true, } in next Js config with root babel.cofig.json
Finally symlink hack worked for external shared files
Use symlinks for shared folder by updating next.config.js
module.exports = {
//...
resolve: {
symlinks: false,
},
};
For anyone who is still having trouble, you don't need any extra package other than our lovely mini-css-extract-plugin. Here is how you solve the issue.
PS: I also added sass to my webpack config. You can use both less and sass/scss files in your project.
next.config.js:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
webpack(config) {
config.module.rules.push(
{
// this part is for css
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, { loader: 'css-loader' }],
},
{
// this part is for sass
test: /\.module\.(scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader' },
{ loader: 'sass-loader' },
],
},
{
// this part is for less
test: /\.less$/i,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
},
{
loader: 'less-loader',
options: {
sourceMap: true,
lessOptions: {
javascriptEnabled: true,
},
},
},
],
}
);
config.plugins.push(
new MiniCssExtractPlugin({
filename: 'static/css/[name].css',
chunkFilename: 'static/css/[contenthash].css',
})
);
return config;
},
};
package.json:
"dependencies": {
"#next/font": "13.1.1",
"css-loader": "^6.7.3",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"mini-css-extract-plugin": "^2.7.2",
"next": "13.1.1",
"next-transpile-modules": "^10.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
}

How to set up Browserify with Elixir and Browserify Shim on Laravel 5?

I am trying to set up Browserify with Elixir and Browserify Shim on Laravel 5.2 to use Gulp with my JavaScript files, but I didn't have much luck so far. This should be pretty straightforward to do, but it isn't.
Here is my package.json
{
"private": true,
"devDependencies": {
"gulp": "^3.8.8"
},
"dependencies": {
"bootstrap-sass": "^3.0.0",
"browserify-shim": "^3.8.12",
"jquery": "^2.2.0",
"jquery-ui": "^1.10.5",
"laravel-elixir": "^4.0.0"
},
"browser": {
"app": "./resources/assets/js/app.js",
"utils": "./resources/assets/js/utils.js",
},
"browserify": {
"transform": [
"browserify-shim"
]
},
"browserify-shim": {
"app": {
"depends": [
"jquery:$",
"utils:Utils"
]
},
"utils": {
"depends": [
"jquery:$"
]
},
}
}
gulpfile.js
var elixir = require('laravel-elixir');
elixir(function (mix) {
mix.browserify('main.js', './public/js/bundle.js');
});
Entry script main.js looks like this:
var $ = require('jquery');
var Utils = require('utils');
var App = require('app');
app.js
var App = {
init: function(){
console.log(Utils);
Utils.doSomething();
}
//other methods
};
In short: Utils depends on $, and App depends on both $ and Utils.
When I hit gulp from terminal, bundle.js is correctly created. All scripts are wrapped up in Browserify code (as expected). Each script has all included dependencies, like I configured in package.json so this part looks good as well.
The problem is that all my included dependencies are empty objects. For example, Utils in app.js is empty, and I get an error when I try to call its method "doSomething". Console log prints out an empty object "{}" instead of real object. The only correctly included script is jQuery and it's not an empty object.
What could be wrong here? Do I need to make some changes in my JS files or in configuration to make this work? It looks like I'm pretty close to the solution, but it still does not work and I can't use it at all.
It is the easiest solution to directly use 'exports' from browserify-shim property:
"browserify-shim": {
"app": {
"exports": "App",
"depends": [
"jquery:$",
"utils:Utils"
]
},
"utils": {
"exports": "Utils",
"depends": [
"jquery:$"
]
},
}
Take a look at this repo which I believe shows the fixed version of your app. The issue is that your app.js and utils.js modules aren't exporting anything to their respective require calls. One option is to add a line like:
module.exports = App;
to the bottom of your app.js file, and the equivalent to the bottom of your utils.js file. You'll see if you test the repo that badapp doesn't have this line and produces the exact behavior you're describing.
See this answer for an explanation of the issue.

"it()" seems to get stuck in jasmine test

I am using karma & jasmine to do unit tests, and I am trying to do my first test. I am using the first example here:
https://jasmine.github.io/1.3/introduction.html#section-Matchers
and it didn't seem to do anything, so I added some logging and tried to make it catch an error:
console.log('a');
describe("A suite", function() {
console.log('b', typeof(it));
it("contains spec with an expectation", function() {
console.log('c');
expect(true).toBe(false);
});
});
And this is what my output comes out with:
Chrome 43.0.2357 (Mac OS X 10.10.3) LOG: 'a'
Chrome 43.0.2357 (Mac OS X 10.10.3) LOG: 'b', 'function'
so it looks like nothing internal to the "it" function gets executed since 'c' is never outputted. Am I missing something?
Update
So this is the grunt task I am running:
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
karma: {
unit: {
configFile: 'karma.conf.js'
}
}
});
grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('default', ['karma']);
};
And this is my package.json with the list of installed npm packages:
{
"name": "abc.com",
"version": "0.0.1",
"private": true,
"dependencies": {
"bcrypt": "^0.8.3",
"body-parser": "^1.0.2",
"bower": "^1.4.1",
"ejs": "^2.3.1",
"email-templates": "^2.0.0-beta.1",
"error-handler": "^0.1.4",
"errorhandler": "^1.3.6",
"express": "~4.1.1",
"express-session": "^1.11.2",
"grunt": "^0.4.5",
"jade": "~0.31.2",
"jasmine": "^2.3.1",
"jasmine-runner": "^0.2.9",
"karma": "^0.12.37",
"karma-chrome-launcher": "^0.2.0",
"karma-jasmine": "^0.3.5",
"karma-junit-reporter": "^0.2.2",
"method-override": "^1.0.0",
"morgan": "^1.0.0",
"mysql": "^2.6.2",
"nodemailer": "^1.3.4",
"protractor": "^1.1.1",
"shelljs": "^0.2.6",
"xoauth2": "^1.0.0"
},
"scripts": {
"prestart": "npm install",
"postinstall": "bower install --allow-root",
"start": "supervisor -n error app.js",
"pretest": "npm install",
"test": "karma start karma.conf.js",
"test-single-run": "karma start karma.conf.js --single-run",
"preupdate-webdriver": "npm install",
"update-webdriver": "webdriver-manager update",
"preprotractor": "npm run update-webdriver",
"protractor": "protractor e2e-tests/protractor.conf.js",
"update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/##NG_LOADER_START##[\\s\\S]*\\/\\/##NG_LOADER_END##/, '//##NG_LOADER_START##\\n' + sed(/sourceMappingURL=angular-loader.min.js.map/,'sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map','app/bower_components/angular-loader/angular-loader.min.js') + '\\n//##NG_LOADER_END##', 'app/index-async.html');\""
},
"configs": {
"client_javascript_paths": [
"public/components/common/helpers.js",
"public/libs/bower_components/html5-boilerplate/js/vendor/modernizr-2.6.2.min.js",
"public/libs/bower_components/jquery/dist/jquery.min.js",
"public/libs/bower_components/angular/angular.js",
"public/libs/bower_components/angular-route/angular-route.js",
"public/libs/bower_components/angular-resource/angular-resource.js",
"public/libs/bower_components/d3/d3.js",
"public/libs/bower_components/c3/c3.js",
"public/libs/bower_components/angular-chart/angular-chart.js",
"public/libs/bower_components/moment/moment.js",
"public/components/common/filters.js",
"public/components/notify/notify.js",
"public/components/static/static.js",
"public/components/account/account.js",
"public/components/auth/auth.js",
"public/components/formatted-table/formatted-table.js",
"public/app.js",
"public/libs/underscore.js"
]
},
"devDependencies": {
"jasmine": "^2.3.1",
"jasmine-core": "^2.3.4",
"karma": "^0.12.37",
"karma-chrome-launcher": "^0.2.0",
"karma-jasmine": "^0.3.5",
"karma-phantomjs-launcher": "^0.2.0",
"phantomjs": "^1.9.17"
}
}
And finally, this is my karma.conf.js
module.exports = function(config) {
var package = require('./package.json')
console.log(package.configs.client_javascript_paths);
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'requirejs'],
// list of files / patterns to load in the browser
files: package.configs.client_javascript_paths.concat([
'public/components/**/*.tests.js'
]),
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false
});
};
and I just run
grunt
to start it to get the output at the top of this.
It is happening because of requirejs framework you use with karma. If you don't need it, then you could just remove it from karma.conf.js and test will work just fine. But if you actually need it, then I would suggest to look at this documentation page, explaining how to configure requirejs for karma, it actually requires an extra file.
Using the files you've presented in the question I was able to execute the tests after the following steps:
first create a backup of karma.conf.js
use CLI command karma init to reinitiate creation of karma config
on the step Do you want to use Require.js ? selected Yes
on the step Do you wanna generate a bootstrap file for RequireJS? selected Yes
copied everything from a backup and added a file that was generated by karma init, it should be called test-main.js, to the list of watched files:
module.exports = function(config) {
var package = require('./package.json');
config.set({
// ...
files: package.configs.client_javascript_paths.concat([
'public/components/**/*.tests.js',
'test-main.js', // here it is
])
// ...
});
};

Resources