Webpack笔记(一)

偶然的一次机会,接触了某大佬的webpack总结,好生喜欢,再次加深了我对webpack的了解,下面就是在一些webpack的个人总结
我们先看下一个webpack.config.js文件

const webpack = require('webpack');
const UglifyJsPlugin=require('uglifyjs-webpack-plugin');

module.exports = {
    devtool: 'eval-source-map',
    // externals: {
    //  'react': 'window.React',
    //  'react-dom': 'window.ReactDOM'
    // },
    entry: __dirname + "/app/main.js",
    output: {
        path: __dirname + "/public",
        filename: "bundle.js"
    },

    module: {
        loaders: [{
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader' //添加对样式表的处理
        }]
    },

    plugins: [
        new UglifyJsPlugin(),
        new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('./manifest.json'),
        })
    ],

    devServer: {} // Omitted for brevity
}

根据这个文件我们来逐步了解一下webpack
一、生成Source-Map
开发总是离不开调试,如果可以更加方便的调试当然就能提高开发效率,不过打包后的文件有时候不容易找到出错的地方对应的源代码位置,Source Maps就是来解决这个问题的。 在webpack的配置文件中配置source maps,需要配置devtool,有四种配置选项

  • source-map: 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包文件的构建速度;
  • cheap-module-source-map: 在一个单独的文件中生成一个不带列映射的map,不带列映射提高项目构建速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便
  • eval-source-map: 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。不过在开发阶段这是一个非常好的选项,但是在生产阶段一定不要用这个选项
  • cheap-module-eval-source-map: 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点;
module.exports = {
    devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
    entry:  __dirname + "/app/main.js",
    output: {
        path: __dirname + "/public",
        filename: "bundle.js"
    }
}

二、使用webpack构建本地服务器
webpack提供一个可选的本地开发服务器,可以检测代码的修改,并自动刷新浏览器的结果。该本地服务器基于node.js构建,在使用前我们需要单独安装作为项目依赖。

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

配置选项:
- contentBase: 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public”目录)
- port: 设置默认监听端口,如果省略,默认为“8080”
- inline: 设置为true,当源文件改变时会自动刷新页面
- historyApiFallback: 在开发单页应用时非常有用,它依赖于HTML5

启动server:

//webpack非全局安装的情况
//linux
./node_modules/.bin/webpack-dev-server
//使用npm
//package.json
"server": "webpack-dev-server"
//运行
npm run server

三、Loaders
使用不同的loader,webpack通过调用外部脚本或者工具可以对不同格式的文件进行处理。 Loaders需要单独安装并且需要在webpack.config.js下的modules关键字下进行配置,Loaders的配置选项包括以下几方面:

  • test: 一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
  • loader: loader的名称(必须
  • include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
  • query: 为loaders提供额外的设置选项(可选)

    安装依赖包:

// npm一次性安装多个依赖模块,模块之间用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom
module.exports = {
    devtool: 'eval-source-map',

    entry: __dirname + "/app/main.js",
    output: {
        path: __dirname + "/public",
        filename: "bundle.js"
    },

    module: {
        loaders: [{
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader', //在webpack的module部分的loaders里进行配置即可
            query: {
                presets: ['es2015', 'react']
            }
        }]
    },
    devServer: {
        contentBase: "./public",
        historyApiFallback: true,
        inline: true
    }
}

因为babel有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 “.babelrc” 的配置文件中

//.babelrc
{
  "presets": ["react", "es2015"]
}

四、css module
webpack提供两个工具处理样式表,css-loader 和 style-loader,二者处理的任务不同,css-loader使你能够使用类似@import 和 url(…)的方法实现 require()的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。

//安装
npm install --save-dev style-loader css-loader
//css-loader配置
module.exports = {
    devtool: 'eval-source-map',

    entry:  __dirname + "/app/main.js",
    output: {
        path: __dirname + "/build",
        filename: "bundle.js"
    },

    module: {
    loaders: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
        },
        {
            test: /\.css$/,
            loader: 'style-loader!css-loader'//添加对样式表的处理
        }]
    },

    devServer: {}
}
import './app.css';//使用require导入css文件

五、解决webpack打包慢的问题
1、Exposing global variables(公开全局变量)
如果想使用一些全局变量,但是不想使他们包含在Webpack包里,可以在webpack.config.js中启用external字段。
你也可以将react和react-dom放到外部,这将大大减少bundle.js的建造时间和建筑尺寸。

//data.js
var data='Hello zhaishuang webpack';
//main.jsx
const data=require('data');
const React=require('react');
const ReactDOM=require('react-dom');
ReactDOM.render(
    <h1>{data}</h1>,
    document.body
)
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>expose global variables</title>
</head>
<body>
<script src="data.js"></script>
<script src="bundle.js"></script>
</body>
</html>
//webpack.config.js
module.exports={
    entry:'./main.jsx',
    output:{
        filename:'bundle.js'
    },
    module:{
        rules:[{
            test:/\.js[x]?$/,
            exclude:/node_modules/,
            use:{
                loader:'babel-loader'
            }
        }]
    },
    externals:{
        'data':'data'
    }
}

2、使用webpack.DllPlugin
只对库文件打包一次。也就是说,只要库文件不变,只需要打包一次,以后再打包业务代码和库文件没关系啦,这样一来真正做到了库文件永远是那个库文件,只要库文件不变,缓存永远有效(Etag不变),打起包来把库丢到脑后,神清气爽。介绍一下最简单的使用方式:首先另写一个 webpack 配置文件,毕竟是单独打包库了,假设 webpack.config.dll.js

/*dll.config.js*/
const path = require('path');
const webpack = require('webpack');

const vendors = [
    'react',
    'react-dom',
    // ...其它库
];
module.exports = {
    output: {
        path: path.resolve(__dirname, './public'),
        filename: '[name].js',
        library: '[name]',
    },
    entry: {
        "lib": vendors,
    },
    plugins: [
        new webpack.DllPlugin({
            path: 'manifest.json',
            name: '[name]',
            context: __dirname,
        }),
    ],
};

webpack.DllPlugin 的选项中:

  • path 是 manifest.json 文件的输出路径,这个文件会用于后续的业务代码打包;
  • name 是dll暴露的对象名,要跟 output.library 保持一致;
  • context 是解析包路径的上下文,这个要跟接下来配置的webpack.config.js 一致。
//package.json
"dll": "webpack --config ./dll.config.js"

运行:

npm run dll

生成 lib.js 和 manifest,json。
接下来打包webpack.config.js:

const webpack = require('webpack');

module.exports = {
    devtool: 'eval-source-map',
    // externals: {
    //  'react': 'window.React',
    //  'react-dom': 'window.ReactDOM'
    // },
    entry: __dirname + "/app/main.js",
    output: {
        path: __dirname + "/public",
        filename: "bundle.js"
    },

    module: {
        loaders: [{
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
        }]
    },

    plugins: [
        new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('./manifest.json'),
        })
    ],

    devServer: {} // Omitted for brevity
}

webpack.DllReferencePlugin 的选项中:
- context 需要跟之前保持一致,这个用来指导 Webpack 匹配 manifest 中库的路径
- manifest 用来引入刚才输出的 manifest.json 文件

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>expose global variables</title>
</head>
<body>
<script src="lib.js"></script>
<script src="bundle.js"></script>
</body>
</html>

其实还有一个速度的优化点,就是配置babel,让它排除一些文件,当loader这些文件时不进行转换,自动跳过;可在.babelrc文件中配置,示例:

{
    "presets": ['es2015', 'react'],
    "ignore":[
        "jquery.js",
        "jquery.min.js",
        "angular.js",
        "angular.min.js",
        "bootstrap.js",
        "bootstrap.min.js"
    ]
}

六、Plugins
1、UglifyJSPlugin
这个插件使用 UglifyJS 去压缩你的JavaScript代码。除了它从 webpack中解耦之外,它和 webpack 核心插(webpack.optimize.UglifyJSPlugin) 是同一个插件

//webpack.config.js
var webpack=require('webpack');
var UglifyJsPlugin=require('uglifyjs-webpack-plugin');

module.exports={
    entry:'./main.js',
    output:{
        filename:'bundle.js'
    },
    plugins:[
        new UglifyJsPlugin()
    ]
};

2、html-webpack-plugin&&open-browser-webpack-plugin(打开新的tab页面)
html-webpack-plugin
1.为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
2.可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口
作用:
直接为项目生成一个或多个HTML文件(HTML文件个数由插件实例的个数决定),并将webpack打包后输出的所有脚本文件自动添加到插件生成的HTML文件中。通过配置,可以将根目录下用户自定义的HTML文件作为插件生成HTML文件的模板。另外,还可以通过向插件传递参数,控制HTML文件的输出。

const HtmlwebpackPlugin=require('html-webpack-plugin');
const OpenBrowserPlugin=require('open-browser-webpack-plugin');

module.exports={
   entry:'./main.js',
   output:{
       filename:'bundle.js'
   },
    plugins:[
        new HtmlwebpackPlugin({
            title:'html-webpack-plugin',
            finename:'index.html'
        }),
        new OpenBrowserPlugin({
            url:'http://localhost:3000'
        })
    ]
}

七、package.json中dependences和devDependencies的区别
devDependencies 是你开发时候用的库, 比如测试库,测试服务器之类的,在真实生产环境是不需要的。
dependencies 是你生产环境需要的依赖库。
如果你使用了一些构建工具,比如webpack之类的,打包的时候,是不会把dev库打进去的。

八、Code splitting
们打包时通常会生成一个大的bundle.js(或者index,看你如何命名)文件,这样所有的模块都会打包到这个bundle.js文件中,最终生成的文件往往比较大。
code splitting就是指将文件分割为块(chunk),webpack使我们可以定义一些分割点(split point),根据这些分割点对文件进行分块,并实现按需加载。第三方类库单独打包。由于第三方类库的内容基本不会改变,可以将其与业务代码分离出来,这样就可以将类库代码缓存在客户端,减少请求。按需加载。webpack支持定义分割点,通过require.ensure进行按需加载。通用模块单独打包。我们代码中可能会有一些通用模块,比如弹窗、分页、通用的方法等等。其他业务代码模块常常会有引用这些通用模块。若按照2中做,则会造成通用模块重复打包。这时可以将通用模块单独打包出来。

//a.js
module.exports='Hello zhaishuang';
//main.js
// Now a.js is requested, it will be bundled into another file
const load=require('bundle-loader!./a.js');
// To wait until a.js is available (and get the exports)
//  you need to async wait for it.
load(function(file){
    document.open();
    document.write('<h1>'+file+'</h1>');
    document.close();
})

九、CommonsChunkPlugin
提取公共包,公共包那就是不只一个地方使用喽,单页应用(单入口)的库只有他自己使用,不能算公共包吧?这个插件提取的公共包,每次是会重新打包的(Etag会不同),无论是节约打包时间,(虽然微不足道的时间但毕竟是无用功:库根本没变么),还是对浏览器缓存的利用(万一 max-age 过期了你就放弃缓存了么?)都不是好的方案。最佳方案浮出水面:DllPlugin

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Commonchunk</title>
</head>
<body>
<div id="a"></div>
<div id="b"></div>
<script src="commons.js"></script>
<script src="bundle1.js"></script>
<script src="bundle2.js"></script>
</body>
</html>
//main1.jsx
const React=require('react');
const ReactDOM=require('react-dom');
ReactDOM.render(
    <h1>Hello world</h1>,
    document.getElementById('a')
)
//main2.jsx
const React=require('react');
const ReactDOM=require('react-dom');
ReactDOM.render(
    <h2>Hello shuangshuang</h2>,
    document.getElementById('b')
)
//webpack.config.js
const webpack=require('webpack');
module.exports={
    entry:{
        bundle1:'./main1.jsx',
        bundle2:'./main2.jsx'
    },
    output:{
        filename:'[name].js'
    },
    module:{
        rules:[
            {
                test:/\.js[x]?$/,
                exclude:/node_modules/,
                use:{
                    loader:'babel-loader'
                }
            }
        ]
    },
    plugins:[
        new webpack.optimize.CommonsChunkPlugin({
            name:'commons',
            filename:"commons.js"
        })
    ]
}

十、Vendor chunk
当多个JS文件拥有公共块时,可以使用插件CommonsChunkPlugin将公共部分提取到一个单独的文件中,这有助于浏览器的缓存和节省带宽。您还可以将脚本中的第三方库从CommonsChunkPlugin中提取到单独的文件中。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vendor chunk</title>
</head>
<body>
<h1></h1>
<script src="vendor.js"></script>
<script src="bundle.js"></script>
</body>
</html>
// main.js
$('h1').text('Hello World');
//webpack.config.js
var webpack = require('webpack');
module.exports = {
    entry: {
        app: './main.js'
    },
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        })
    ]
};

如果你想在每个模块中使用一个模块作为全局变量,
比如在没有写入require(“jquery”)的情况下在每个模块中都可以使用$和jQuery。 你应该使用ProvidePlugin,它可以自动加载模块,而不必在任何地方导入或需要它们。

猜你喜欢

转载自blog.csdn.net/michellezhai/article/details/80097597