Related
I have a fairly complex web app written in React/Redux and webpack for compilation. Its landing page consists of 2 images and the main app module. All the other modules are lazy-loaded depending on what the user wants to do. When I audit using google devtools, I get a performance score of 74, which isn't bad.
But the initial page loads in over 15 seconds on the iphones! And I need to optimize it.
Images One of the images is the background of html body, so that it shows when other pages are loading. The other one is the background of the Home page component. The home page image is non-negotiable, it must be there. The one in the body I'm more flexible about, but it looks cool.
Right now the Home page image is imported into the react component using the webpack url-loader and is therefore in the app bundle. Is that the best way? The other image is loaded in the index.html on the body element directly. I'm not sure which is the fastest way.
I'm not an image expert either, so maybe is there something else I can do to compress or optimize the images? Is there a "best size" for use cross-platform? Also what tools to use to change? I have GIMP on my system, but can use something else.
Splash It would be nice if the user sees "something" when it's loading right away, so they can be more patient. Right now they only see a blank white screen. I have following all the favicon generator and have them all setup according to directions. But the splash is not showing. Is there something I can do there? I have even tried to alter right in the HTML a different color background, but that doesn't show up either.
CSS To organize my project code, I built everything very componentized. My stylesheets mostly sit alongside each component and are imported into where it's used. These also get bundled by webpack using miniCssExtractLoader and css-loader. I attach my webpack configs -- is there something I can do there?
Webpack What else can I do to get the initial load time down? Here are my webpack.common and webpack.prod setups. Any ideas will be appreciated!
webpack.common.js
const path = require('path');
var webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const sourcePath = path.join(__dirname, './src');
const autoprefixer = require('autoprefixer');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js'
},
output: {
filename: '[name].[chunkhash:4].js',
chunkFilename: '[name].[chunkhash:4].js', //name of non-entry chunk files
path: path.resolve(__dirname, 'dist'), //where to put the bundles
publicPath: "/" // This is used to generate URLs to e.g. images
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
use: [
{
loader: "html-loader",
options: { minimize: true }
}
]
},
{
test: /\.(scss|sass|css)$/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader' },
{ loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer({ grid: false})]
}
},
{
loader: 'fast-sass-loader',
options: {
includePaths: [ path.resolve(__dirname, 'src'), path.resolve(__dirname, 'src','styles') ,'./node_modules', '/node_modules/materialize-css/sass/components'],
sourceMap: true
}
}
]
},
{
test: /\.(jpg|png)$/,
loader: 'url-loader',
options: {
limit: 8192 // inline base64 URLs for <=8k images, direct URLs for the rest
},
},
{
test: /\.svg/,
use: {
loader: 'svg-url-loader',
options: {}
}
}
]
},
resolve: {
alias: {
components: path.resolve(__dirname, 'src', 'components'),
navigation: path.resolve(__dirname, 'src', 'navigation'),
reducers: path.resolve(__dirname, 'src', 'reducers'),
actions: path.resolve(__dirname, 'src', 'actions'),
routes: path.resolve(__dirname, 'src', 'routes'),
utils: path.resolve(__dirname, 'src', 'utils'),
styles: path.resolve(__dirname, 'src', 'styles'),
images: path.resolve(__dirname, 'public', 'images'),
public: path.resolve(__dirname, 'public'),
test: path.resolve(__dirname, 'src', 'test'),
materialize: path.resolve(__dirname, 'node_modules', 'materialize-css', 'sass', 'components')
},
// extensions: ['.webpack-loader.js', '.web-loader.js', '.loader.js', '.js', '.jsx'],
modules: [
path.resolve(__dirname, 'node_modules'),
sourcePath
]
},
optimization: {
splitChunks: {
cacheGroups: {
js: {
test: /\.js$/,
name: "commons",
chunks: "all",
minChunks: 7,
},
styles: {
test: /\.(scss|sass|css)$/,
name: "styles",
chunks: "all",
enforce: true
}
}
}
},
plugins: [
new CleanWebpackPlugin(['dist']),
new CopyWebpackPlugin([ { from: __dirname + '/public', to: __dirname + '/dist/public' } ]),
new MiniCssExtractPlugin({filename: "[name].css"}),
new webpack.NamedModulesPlugin(),
new HtmlWebpackPlugin({
"template": "./src/template.html",
"filename": "index.html",
"hash": false,
"inject": true,
"compile": true,
"favicon": false,
"minify": true,
"cache": true,
"showErrors": true,
"chunks": "all",
"excludeChunks": [],
"title": "ShareWalks",
"xhtml": true,
"chunksSortMode": 'none' //fixes bug
})
]
};
webpack.prod.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
plugins: [
new WorkboxPlugin.GenerateSW({
// these options encourage the ServiceWorkers to get in there fast
// and not allow any straggling "old" SWs to hang around
clientsClaim: true,
skipWaiting: true
}),
]
});
Your question is too broad for SO and will be closed :) Lets concentrate on "how to make bundle smaller" optimization path.
1.try babel loose compilation (less code)
module.exports = {
"presets": [
["#babel/preset-env", {
// ...
"loose": true
}]
],
}
2.also review your polyfills, use minification, learn webpack null-loader techique.
3.there is a hope that more aggresive chunking could give some positive effect (if not all is used on each your app page, then it can be lazy loaded).
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: infinity,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
vendorname(v) {
var name = v.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${name.replace('#', '_')}`;
},
},
},
I have a problem with setting up a test environment for a single page application. I am able to run my tests with headless chrome via karma and mocha but I can´t write tests with ES6 Syntax.
My current start command is
karma start --browsers ChromeHeadless karma.config.js --single-run
my karma.config.js
module.exports = function(config) {
config.set({
frameworks: ['mocha', 'chai'],
files: ['test/**/*spec.js'],
reporters: ['nyan'],
port: 9876, // karma web server port
colors: true,
logLevel: config.LOG_INFO,
browsers: ['ChromeHeadless'],
autoWatch: true,
singleRun: false, // Karma captures browsers, runs the tests and exits
concurrency: Infinity,
})
}
I am able to write normal tests but cant use ES6 Syntax here. When I try to import some react components I get this error:
HeadlessChrome 0.0.0 (Linux 0.0.0)
Uncaught SyntaxError: Unexpected token import
at http://localhost:9876/base/test/components.spec.js?b89d2ba6de494310860a60ad2e9e25aea5eb3657:2
So I have to setup babel somehow to compile my test files first. When I try to use compilers: ['js:babel-core/register'] in my karma config its not gonna work.
I also have seen that compilers seems to be deprecated soon so I also tried require: ['babel-core/register'] but it still won´t compile to use ES6 for my test files.
Any idea how to configurate my karma file to write my tests with ES6 ?
Just in case its important. This is my webpack.config.js
const path = require('path');
const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
inject: 'body'
});
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve('dist'),
filename: 'index_bundle.js'
},
module: {
loaders: [
{test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/},
{test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/}
]
},
plugins: [
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, 'src/sw.js'),
}),
HtmlWebpackPluginConfig
],
devServer: {
hot: false,
inline: false,
historyApiFallback: true
}
};
To make things more clear here is a sample project (it's fully runnable, you can fill out files and play around). Just two things to mention: I used jamsine instead of mocha and real 'Chrome' browser instead of headless. Runnable via npm run test command.
files structure
/
karma.conf.js
package.json
sample.js
sampleTest.js
webpack.test.config.js
karma.conf.js:
// Karma configuration
module.exports = function(config) {
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'],
// list of files / patterns to load in the browser
files: ['*Test.js'],
// list of files to exclude
exclude: [],
// preprocess matching files before serving them to the browser
preprocessors: {
'*Test.js': [ 'webpack'] //preprocess with webpack
},
// test results reporter to use
reporters: ['progress'],
// setting up webpack configuration
webpack: require('./webpack.test.config'),
// 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
browsers: ['Chrome'],
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level how many browser should be started simultaneous
concurrency: Infinity
})
}
package.json (only relevant stuff):
{
"scripts": {
"test": "node_modules/karma/bin/karma start karma.conf.js"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"jasmine-core": "^2.8.0",
"karma": "^2.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-jasmine": "^1.1.1",
"karma-webpack": "^2.0.9",
"webpack": "^3.10.0"
}
}
sample.js:
export default function(data){
return data;
}
sampleTest.js:
import sample from 'sample';
describe('Sample', function(){
it('is defined', function(){
expect(sample).toBeDefined();
});
it('returns argument', function(){
expect(sample(0)).toBe(0);
})
});
webpack.test.config.js:
module.exports = {
module: {
rules: [
{
test: /tests\/.*\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
}
]
},
resolve: {
modules: ["node_modules", './'],
extensions: [".js"]
}
};
Karma's webpack plugin is used to inform karma that it should prepare files using webpack and specific webpack configuration before sending them to the browser.
Please note key points:
test files pattern in karma.conf.js
pattern to preprocess files (should match the pattern above)
webpack entry in karma.conf.js file
module entry in webpack.test.config.js
p.s. personally I don't use separate patterns for files, I use a separate file (named, say, tests.webpack.js) to have a single place where the way to find test files is defined:
//make sure you have your directory and regex test set correctly
var context = require.context('.', true, /.*Test\.js$/i);
context.keys().forEach(context);
and have in karma.conf.js (paths are irrelevant to sample project above):
files: [
'tests/tests.webpack.js',
],
preprocessors: {
'./tests/tests.webpack.js': [ 'webpack'] //preprocess with webpack
}
You need to convert ESModule in commonjs module with the babel-plugin-transform-es2015-modules-commonjs plugin
In your .babelrc file :
{
"plugins": [
"transform-es2015-modules-commonjs"
]
}
Update :
You can set the plugin in your webpack configuration :
{
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: [require('#babel/plugin-transform-es2015-modules-commonjs')]
}
}
I am trying to setup the simplest possible typescript+react-project in visual studio 2017.
I have this webpack.config.js that was changed to work with webpack 2+
The change was from using "loaders" and "preloaders" to "rules".
With the content below I get the error
Module build failed: error TS5014: Failed to parse file 'C:\Users\my project\tsconfig.json': Unexpected token } in JSON at position 4521.
This is the file
module.exports = {
entry: "./Scripts/hello-stackoverflow/index.tsx",
output: { filename: "./dist/bundle.js" },
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".ts", ".tsx", ".js", ".json"]
},
module: {
rules: [
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.tsx?$/, loader: "ts-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
]
},
externals: {
"react": "React",
"react-dom": "ReactDOM"
}
};
I'm struggling to implement material-components-web in a React application properly with Webpack 2. I want to import the Sass files so they can be themed.
Here's what I think are relevant parts of my config:
var webpackConfig = module.exports = {
context: path.resolve(__dirname, '..'),
entry: {
'main': [
'./src/theme/main.scss',
'./src/client.js'
]
},
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: true,
importLoaders: 3,
sourceMap: true,
localIdentName: '[local]___[hash:base64:5]'
}
}, {
loader: 'autoprefixer-loader',
options: {
browsers: 'last 2 version'
}
}, {
loader: 'resolve-url-loader',
}, {
loader: 'sass-loader', // compiles Sass to CSS
options: {
outputStyle: 'expanded',
sourceMap: true,
includePaths: ['../src', '../node_modules', '../node_modules/#material/*']
.map(d => path.join(__dirname, d))
.map(g => glob.sync(g))
.reduce((a, c) => a.concat(c), [])
}
},
],
}
]
},
resolve: {
modules: [
'src',
'node_modules'
],
extensions: ['.json', '.js', '.jsx', '.scss']
}
};
and I start my main.scss with this:
$mdc-theme-primary: #4a90e2;
$mdc-theme-accent: #f22745;
$mdc-theme-background: #fff;
#import '~material-components-web/material-components-web.scss';
All my app Sass files load fine, but the material-components-web import doesn't seem to work at all but also doesn't throw any errors.
If I add 'material-components-web/dist/material-components-web.min.css' to entry.main then it works but then I'm obviously unable to change the theme as easily so that seems wrong. What should I do here?
Please check the latest documentation about importing the default theme here: https://github.com/material-components/material-components-web/blob/master/docs/theming.md#step-3-changing-the-theme-with-sass
I followed 100% and it works for me using React to, according to this you probably want to change the line
#import '~material-components-web/material-components-web.scss';
to
#import "material-components-web/material-components-web";
and webpack 2 should be able to handle it.
Let us know if you found a solution.
Is it possible to write unit tests for VueJs if you are using Laravel's Elixir for your webpack configuration?
VueJs 2x has a very simple example for a component test: Vue Guide Unit testing
<template>
<span>{{ message }}</span>
</template>
<script>
export default {
data () {
return {
message: 'hello!'
}
},
created () {
this.message = 'bye!'
}
}
</script>
and then...
// Import Vue and the component being tested
import Vue from 'vue'
import MyComponent from 'path/to/MyComponent.vue'
describe('MyComponent', () => {
it('has a created hook', () => {
expect(typeof MyComponent.created).toBe('function')
})
it ...etc
})
and gives an example of a karma conf file here: https://github.com/vuejs-templates
But the Karma configuration file requires a webpack configuration file
webpack: webpackConfig,
The only problem is the Laravel's Elixir is creating the webpack configuration so it can't be included.
I have tried creating another webpack configuration file based on the example from https://github.com/vuejs-templates/webpack.
Something like this:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
and included it like...
// Karma configuration
// Generated on Wed Mar 15 2017 09:47:48 GMT-0500 (CDT)
var webpackConf = require('./karma.webpack.config.js');
delete webpackConf.entry;
module.exports = function(config) {
config.set({
webpack: webpackConf, // Pass your webpack.config.js file's content
webpackMiddleware: {
noInfo: true,
stats: 'errors-only'
},
But I am getting errors that seem to indicate that webpack isn't doing anything.
ERROR in ./resources/assets/js/components/test.vue
Module parse failed: /var/www/test/resources/assets/js/components/test.vue Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
| <template>
| <span >{{test}}</span>
| </template>
Ok, I got this to work. Couple of things that might help.
I was originally running gulp, and trying to run tests in my vagrant box, to try to match the server configuration. I think that makes it much harder to find examples and answers on the internet.
Ok, so the main problem I was having is that webpack wasn't processing my components included in my test files. I copied the webpack config out of the laravel-elixir-vue-2/index.js node module directly into the Karma configuration file and it started working.
The key is that karma-webpack plugin needs both the resolve and module loader configuration settings (resolve with alias and extensions) for it to work.
Hope this helps someone.
karma.conf.js:
module.exports = function (config) {
config.set({
// to run in additional browsers:
// 1. install corresponding karma launcher
// http://karma-runner.github.io/0.13/config/browsers.html
// 2. add it to the `browsers` array below.
browsers: ['Chrome'],
frameworks: ['jasmine'],
files: ['./index.js'],
preprocessors: {
'./index.js': ['webpack']
},
webpack: {
resolve: {
alias: {
vue: 'vue/dist/vue.common.js'
},
extensions: ['.js', '.vue']
},
vue: {
buble: {
objectAssign: 'Object.assign'
}
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'file-loader',
query: {
limit: 10000,
name: '../img/[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: '../fonts/[name].[hash:7].[ext]'
}
}
]
}
},
webpackMiddleware: {
noInfo: true,
},
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' },
]
},
});
};
I ran into the exact same problem. The accepted answer did not fully work for me. The following solved my issue:
Install relevant loaders for webpack:
npm install --save-dev vue-loader file-loader url-loader
Create webpack config file (note the format). The accepted answer produced errors citing invalid format of the webpack.config.js file. At least with me it did.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
use: [
{ loader: 'vue-loader' }
]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'file-loader',
query: {
limit: 10000,
name: '../img/[name].[hash:7].[ext]'
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: [
{
loader: 'url-loader',
query: {
limit: 10000,
name: '../fonts/[name].[hash:7].[ext]'
}
}
]
}
]
}
}
karma.conf.js
// Karma configuration
var webpackConf = require('./webpack.config.js');
delete webpackConf.entry
module.exports = function(config) {
config.set({
frameworks: ['jasmine'],
port: 9876, // web server port
colors: true,
logLevel: config.LOG_INFO,
reporters: ['progress'], // dots, progress
autoWatch: true, // enable / disable watching files & then run tests
browsers: ['Chrome'], //'PhantomJS', 'Firefox',
singleRun: true, // if true, Karma captures browsers, runs the tests and exits
concurrency: Infinity, // how many browser should be started simultaneous
webpack: webpackConf, // Pass your webpack.config.js file's content
webpackMiddleware: {
noInfo: true,
stats: 'errors-only'
},
/**
* base path that will be used to resolve all patterns (eg. files, exclude)
* This should be your JS Folder where all source javascript
* files are located.
*/
basePath: './resources/assets/js/',
/**
* list of files / patterns to load in the browser
* The pattern just says load all files within a
* tests directory including subdirectories
**/
files: [
{pattern: 'tests/*.js', watched: false},
{pattern: 'tests/**/*.js', watched: false}
],
// list of files to exclude
exclude: [
],
/**
* pre-process matching files before serving them to the browser
* Add your App entry point as well as your Tests files which should be
* stored under the tests directory in your basePath also this expects
* you to save your tests with a .spec.js file extension. This assumes we
* are writing in ES6 and would run our file through babel before webpack.
*/
preprocessors: {
'app.js': ['webpack', 'babel'],
'tests/**/*.spec.js': ['babel', 'webpack']
},
})
}
Then run karma start and everything should work.