Manjushaka / blog

3 stars 0 forks source link

webpack #29

Open Manjushaka opened 6 years ago

Manjushaka commented 6 years ago

步骤:

1.

mkdir webpack-demo cd webpack-demo npm init -y // 创建package.json文件 npm install webpack webpack-cli --save-dev // webpack-cli,webpack的命令行工具。

创建目录结构

  dist
     -index.html
  src
     -index.js
  package.json

package.json增加private: true,去掉main: index.js

npm install --save lodash npx webpack // ./src/index.js -> ./dist/main.js

2. 上面的是命令行输入webpack的命令,我们可以使用配置文件进行webpack的配置。 新建webpack.config.js文件,输入以下内容(默认配置也是这样):

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
};

npx webpack --config webpack.config.js

//  这里可以省略--config webpack.config.js。如果目录里存在webpack.config.js 文件,默认会使用这个文件的配置。

  1. 在命令行运行本地安装的npx命令仍然累赘,可以在npm 的scripts中加入运行webpack的命令。 修改package.json文件:

    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack" //  这个是新加的
    },

    通过向 npm run build 命令和你的参数之间添加两个中横线,可以将自定义参数传递给 webpack,例如:npm run build -- --colors

Manjushaka commented 6 years ago
  1. 没有用到的js资源,webpack就不会打包进去。例如,项目中有一个extra.js文件,但是没有任何一个文件引用它,那么extra.js文件将不会被打包进去。如果仅仅是import,接下里的代码里面没有使用该import的文件,该被import的文件仍会被打包进去。

  2. 直接使用react-router,是无法刷新命中路由的,需要对服务器进行相应配置: 1) nodejs:

    devServer: {
        contentBase: './dist',
        historyApiFallback: true,
        hot: true,
        port: 3013,
    },

    2)ngix

Manjushaka commented 6 years ago

管理资源:

1.样式 css

npm install --save-dev style-loader css-loader

然后修改webpack.config.js文件:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                ]
            }
        ]
    }
};

然后就可以在js文件里面直接import './style.css'了。 如果还想处理其他的less,sass文件等,就需要对应的loader。如postcss, less

2. 图片image

npm install --save-dev file-loader

然后修改webpack.config.js文件:

        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            }
        ]

然后就可以:

import MyImage from './my-image.png'

.hello {
    color: #f00;
    background: url('./back.png');
}

<img src="./my-image.png" />

Check out the image-webpack-loader and url-loader for more on how you can enhance your image loading process.

3. 字体fonts 使用和图片相同的file-loader, 修改webpack.config.js文件:

        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            }
        ]

然后在./src目录下加入需要的字体文化,

+ @font-face {
+   font-family: 'MyFont';
+   src:  url('./my-font.woff2') format('woff2'),
+         url('./my-font.woff') format('woff');
+   font-weight: 600;
+   font-style: normal;
+ }

  .hello {
    color: red;
+   font-family: 'MyFont';
    background: url('./icon.png');
  }

4. 数据data JSON文件,内置可以使用。例如import Data from './data.json' CSVs, TSVs, XML, 等需要csv-loader,xml-loader。

npm install --save-dev csv-loader xml-loader

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(csv|tsv)$/,
                use: [
                    'csv-loader'
                ]
            },
            {
                test: /\.xml$/,
                use: [
                    'xml-loader'
                ]
            }
        ]
    }
};
Manjushaka commented 6 years ago

管理输出

1. HtmlWebpackPlugin 使用hash文件名和多个bundle的时候,无法手动的在html里面,因为文件名称和文件数量一直在改变。那么需要使用HtmlWebpackPlugin来自动管理输出资源。它会在dist目录自动生成一个index.html,可以覆盖你自己的。

npm install --save-dev html-webpack-plugin

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js',
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: 'Output Manament',
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
};

如果想使用更加丰富的配置index.html,可以使用html-webpack-template

2. 清除目录

npm install --save-dev clean-webpack-plugin

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js',
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Output Manament',
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
};
Manjushaka commented 6 years ago

开发配置

1. 使用source maps 打包后的代码到源代码的映射,出现错误或警告可以定位到源代码的具体位置。否则只能显示打包后代码的位置。 修改webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js',
    },
    devtool: 'inline-source-map', // 新加的。
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Development',
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/'
    },
};

可以进行相应的配置,source map 配置

2. 选择合适的开发工具: 1)webpack's Watch Mode 可以指示 webpack "watch" 依赖图中的所有文件以进行更改。如果其中一个文件被更新,代码将被重新编译,所以你不必手动运行整个构建。但是并不会自动的刷新浏览器。 修改package.json:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch", //  新加的
    "build": "webpack"
  },

npm run watch

2)webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。

npm install --save-dev webpack-dev-server

修改webpack.config.js.告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件:

    devServer: {
        contentBase: './dist', // 告诉开发服务器(dev server),在哪里查找文件
    },

修改package.json:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch",
    "start": "webpack-dev-server --open", // 新加的
    "build": "webpack"
  },

npm start

浏览器自动加载页面。如果现在修改和保存任意源文件,web 服务器就会自动重新加载编译后的代码.webpack-dev-server配置选项 3)webpack-dev-middleware 一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。webpack-dev-server内部使用的就是它,可以提供更加灵活的自定义配置。 配合express使用:

npm install --save-dev express webpack-dev-middleware

修改webpack.config.js.

    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/' // publicPath 也会在服务器脚本用到,以确保文件资源能够在 http://localhost:3000 下正确访问
    },

新建server.js

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config');
const compiler = webpack(config);

app.use(webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath
}));

app.listen(3001, function () {
    console.log('webpack dev middleware on port 3001\n');
});

修改package.json:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch",
    "start": "webpack-dev-server --open",
    "server": "node server.js",
    "build": "webpack"
  },

npm run server

浏览器没有自动加载页面,只是会重新编译,编译完后浏览器的代码仍是老代码,应该需要其他的相关热替换配置。

Manjushaka commented 6 years ago

模块热替换Hot Module Replacement,HMR

允许在运行时更新各种模块,而无需进行完全刷新。只适用于开发环境。没有启用HMR的话,修改文件相当于按了刷新键,浏览器会整个的完全重新刷新。而启用了HMR的话,只是局部刷新,没有更新的地方不会变,例如修改了页面的某段文本,改了某个文件后,可能仍是修改后的文本。

1. 启用HMR webpack-dev-server,使用内置的HMR插件。 webpack-dev-middleware,使用webpack-hot-middleware。 修改webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
    entry: {
        app: './src/index.js',
    },
    devtool: 'inline-source-map',
    devServer: {
        contentBase: './dist',
        hot: true, // HMR
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Development',
        }),
        new webpack.HotModuleReplacementPlugin() // HMR
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/'
    },
};

这种启用方法并不会改变事件的回调函数,也就是说,如果callback改变了,点击事件调用的还是原来的callback。需要重新绑定。可以使用 ??? 来解决这个问题。

  1. Node.js启用HMR又不太一样,参考官网吧。

3. 处理css 只要安装了css-loader 和 style-loader,然后启用HMR就可以。

4. 实时调整react组件 react-hot-loader

Manjushaka commented 6 years ago

Tree Shaking

移除无用代码dead code。使用import export。

1. 将文件标记为无副作用(side-effect-free)。 package.json 文件中添加一个配置

{
  "name": "webpack-demo",
  "side-effect": false, // "side-effect": ["./filename.js", "*.css"],
}

2. 压缩输出 -p编译标记,启用uglifyjs压缩插件 或者webpack.config.js的mode配置为production。

Manjushaka commented 6 years ago

配置生产环境

这章内容没看明白,还是不知道怎么弄生产环境

1. 配置 将原来的webpack.config.js 拆分为不同的文件webpack.common.js、webpack.dev.js、webpack.prod.js,然后使用工具webpack-merge进行合并。

npm install --save-dev webpack-merge

然后在package.json中的scripts进行运行:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch",
    "start": "webpack-dev-server --open --config webpack.dev.js",
    "server": "node server.js",
    "build": "webpack --config webpack.prod.js"
  },

2. 压缩代码: UglifyJSPlugin BabelMinifyWebpackPlugin ClosureCompilerPlugin

3. source mapping dev: inline-source-map prod: source-map

4. 压缩css

Manjushaka commented 6 years ago

代码分离code splitting

把代码分离成不同的bundle,实现按需加载或并行下载。

1. 入口 entry points 可以配置多个entry。但是如果多个entry里面都import lodash,那么lodash将会被多次打包,放进多个bundle中。即,如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。 可以用SplitChunks来移除重复。

2. 防止重复 CommonsChunkPlugin不被webpack 4支持,可以使用SplitChunksPlugin。 其他可使用的分离代码的plugins/loaders:mini-css-extract-plugin bundle-loader promise-loader

module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js',
        another: './src/another-module.js',
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
    ],
    optimization: {
        splitChunks: {
            chunks: 'all',
        },
    },
};

如果index.js 和 another-module.js都import 了lodash,那么将抽取出一个单独的bundle(vendors~another~index.bundle).

3. 动态import (1)使用import(),内部使用的是promise实现的。 (2)require.ensure

Prefetching/Preloading modules

bundle analysis

Manjushaka commented 6 years ago

Lazy Loading 懒加载,按需加载

1. import()的使用 webpack.config.js

module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js',
    },
    output: {
        filename: '[name].bundle.js',
        chunkFilename: '[name].chunk.bundle.js', // _按需加载的文件名,import()_
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin(),
    ],
};

index.js

import _ from 'lodash';
function component() {
    var element = document.createElement('div');
    var button = document.createElement('button');
    var br = document.createElement('br');

    button.innerHTML = 'click me and lok at the console';
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.appendChild(br);
    element.appendChild(button);
    button.onclick = e => {
        console.log('before import.');
        import(/* webpackChunkName: "print" */ './print.js').then(module => {
            var print = module.default;

            print();
        })
        console.log('after import.');
    }

    return element;
}

document.body.appendChild(component());

print.js

import moment from 'moment';

console.log('the print.js module has loaded!');

export default () => {
    console.log('button clicked.', moment());
}

刷新页面,network:index.html index.bundle.js 输出: 无 第一次点击:vendors~print.chunk.bundle.js(print.js import moment, load moment.js) print.chunk.bundle.js(load print.js) 输出:before import after import the print.js module has loaded! button clicked (Moment object) 第二次点击:无 输出:before import after import button clicked (Moment object)

2. 在react中的使用,其他的见官网 react-router and react-loadable

Manjushaka commented 6 years ago

Caching 缓存

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js',
    },
    output: {
        filename: '[name].[contenthash].js', // contenthash, 每次内容改变时,生成不一样的hash值
        // chunkFilename: '[name].chunk.bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'caching'
        }),
        new webpack.HashedModuleIdsPlugin(),
    ],
    optimization: {
        runtimeChunk: 'single',
        splitChunks: {  // 对于相同的依赖每次生成的hash值相同,不必每次部署新的版本时都重新下载依赖库
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all',
                }
            }
        }
    }
};