webpack4.0

都已经8102年了,你能手写一个webpack配置吗o。o

Posted by Liangyuqi on June 25, 2018
view times

前言

本篇文章主要还是入门教程,如果对webpack配置已经十分熟悉,想要了解怎么样合理利用缓存,提高打包效率,推荐这篇文章手摸手,带你用合理的姿势使用webpack4

一、什么是webpack

WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。

const path = require('path');  //引入node的path模块
const webpack = require('webpack'); //引入的webpack,使用lodash
const HtmlWebpackPlugin = require('html-webpack-plugin')  //将html打包
const ExtractTextPlugin = require('extract-text-webpack-plugin')     //打包的css拆分,将一部分抽离出来  
const CopyWebpackPlugin = require('copy-webpack-plugin')
//测试下 console.log(path.resolve(__dirname,'dist')); //物理地址拼接
module.exports = {

//webpack的配置文件是一个node.js的module,用CommonJS风格来书写

}

二、entey:配置入口文件的地址

所谓的入口,就是告诉webpack从哪里开始并通过依赖关系图得知要哪些文件要打包。你可以理解为应用程序最先读取的文件。读取这个文件之后,就会产生一个模块依赖关系图,那么当前这个文件,就是依赖关系的根。

key可以是简单的字符串,比如:’app’, ‘main’等。并且对应着output.filename配置中的[name]变量。key还可以是路径字符串。此时webpack会自动生成路径目录,并将路径的最后作为[name]。这个特性在多页面配置下也是很有用的。

entry: {
    'path/of/entry': './deep-app.js',
    'app': './app.js'
}
output: {
    path: './output',
    filename: '[name].js'
}

二、output:配置输出文件的地址

配置output属性就是告诉webpack把编译后的文件写在磁盘的哪个地方。注意一点,虽然入口中可以配置成多个文件的形式,但是输出只能指定一个。

— 打包之后资源,并不总是写在磁盘上,也可能是在内存中,比如使用webpack-dev-server的时候。

__dirname 总是指向被执行 js 文件的绝对路径。
const PROJECT_ROOT = path.resolve(__dirname, ‘../’);

output: {      
        path: path.resolve(PROJECT_ROOT, "./dist");,//定位,输出文件的目标路径
        filename: `${JS_DIR}/[name].[${env.prod?'chunkhash':'hash'}:8].js`
        //文件名[name].js默认,对应entry的key,也可自行配置
        publicPath: "http://cdn.example.com/assets/"  
        //通常如果静态资源(非html 文件)由独立的静态资源服务器提供务,
        // 或者是分配了单独的静态资源访问路径时需要设置此前缀。
        // 通常如果静态资源与html文件保持构建时的目录结构时,走相对路径,无需设置。	   		 }

filename属性表示的是如何命名生成出来的入口文件,规则有以下三种:

[name],指代入口文件的name,也就是上面提到的entry参数的key,因此,我们可以在name里利用/,即可达到控制文件目录结构的效果。

[hash],指代本次编译的一个hash版本,值得注意的是,只要是在同一次编译过程中生成的文件,这个[hash]的值就是一样的;由于每次使用 webpack 构建代码的时候,此 hash 字符串都会更新,因此相当于强制刷新浏览器缓存

[chunkhash],指代的是当前chunk的一个hash版本,也就是说,在同一次编译中,每一个chunk的hash都是不一样的;而在两次编译中,如果某个chunk根本没有发生变化,那么该chunk的hash也就不会发生变化。这在缓存的层面上来说,就是把缓存的粒度精细到具体某个chunk,只要chunk不变,该chunk的浏览器缓存就可以继续使用。

三、module:配置模块,主要用来配置不同文件的加载器(loader)

当你为你的模块安装一个依赖模块时,正常情况下你得先安装他们(在模块根目录下npm install module-name),然后连同版本号手动将他们添加到模块配置文件package.json中的依赖里(dependencies)。

–save(-S)和–save-dev(-D)可以省掉你手动修改package.json文件的步骤。‘

npm install module-name -save 自动把模块和版本号添加到dependencies部分
npm install module-name -save-dev 自动把模块和版本号添加到devdependencies部分

好处:执行命令时,要寻找该模块定义,先从dev目录开始寻找,相比较于全局安装,速度就非常快了。

  • test:匹配处理文件的扩展名的正则表达式
  • use:loader名称,就是你要使用模块的名称
  • include/exclude:手动指定必须处理的文件夹或屏蔽不需要处理的文件夹
  • query:为loaders提供额外的设置选项

loader的三种写法 1. use 2.loader 3.use+loader

1.支持加载css文件

module: {
    rules:[
       {
            test:/\.css$/,
            use:['style-loader','css-loader'],
            include:path.join(__dirname,'./src'),
            exclude:/node_modules/
       }        
    ]
}

加载器的加载顺序为从右至左。即先用css-loader解析然后用style-loader将解析后的css文件添加到Head标签中。

2.支持图片

  • file-loader 解决CSS等文件中的引入图片路径问题
  • url-loader 当图片较小的时候会把图片BASE64编码,大于limit参数的时候还是使用file-loader 进行拷贝

      {
          test: /\.(png|jpg|gif|svg|bmp|eot|woff|woff2|ttf)$/,
          loader: {
              loader: 'url-loader',
              options: {
                  limit: 5 * 1024,// 图片大小 > limit 使用file-loader, 反之使用url-loader
                  outputPath: 'images/'// 指定打包后的图片位置
              }
          }
      }
    

3.编译less 和 sass(打包后在js中引入样式,会有延时问题,生产模式推荐plugins)

	{
        test: /\.css$/,
        use: [
            {
                loader: "style-loader"
            },
            {
                loader: "css-loader"
            }
        ]
    },
    {
        test: /\.less/,
        use: [
            {
                loader: "style-loader"
            },
            {
                loader: "css-loader"
            },
            {
                loader: "less-loader"
            }
        ]
    },
    {
        test: /\.scss/,
        use: [
            {
                loader: "style-loader"
            },
            {
                loader: "css-loader"
            },
            {
                loader: "sass-loader"
            }
        ]
   		 }

4.转义ES6/ES7/JSX

{
   test: /\.js$/,
   exclude: /(node_modules|bower_components)/,
   loader: 'babel-loader',
   options: {
   			  presets: ["env","stage-0","react"]// env --> es6, stage-0 --> es7, react --> react
            // presets 由 .babelrc 配置
            //
            //  import() 本来webpack支持,但是用了babel需要插件才能支持	            plugins: [
                "syntax-dynamic-import",
                "dual-import",
                ["import", {"libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
            ]
        }
}

都8102了,请不要在使用babel-preset-esxxxx系列了,请用babel-preset-env

四、plugins:配置插件

1.自动产出html

我们希望自动能产出HTML文件,并在里面引入产出后的资源。

const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
    new HtmlWebpackPlugin({
        template: './src/index.html',   // 指定产出的模板
        filename: 'base.html',          // 产出的文件名
        chunks: ['common', 'base'],     // 在产出的HTML文件里引入哪些代码块
        hash: true,                     // 名称是否哈希值
        title: 'base',                  // 可以给模板设置变量名,在html模板中调用 htmlWebpackPlugin.options.title 可以使用
        minify: {                       // 对html文件进行压缩
            removeAttributeQuotes: true // 移除双引号
        }
    })
] #### 2.分离CSS 因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来加载

const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
let cssExtract = new ExtractTextWebpackPlugin(`${CSS_DIR}/[name].[contenthash:8].css`);
let lessExtract = new ExtractTextWebpackPlugin(`${CSS_DIR}/[name].[contenthash:8].css`);
let sassExtract = new ExtractTextWebpackPlugin(`${CSS_DIR}/[name].[contenthash:8].css`);

...

module: {
    rules: [
         {
            test: /\.css$/,
            loader: cssExtract.extract({
                use: ['css-loader?minimize']
            })
        }, {
            test: /\.less$/,
            loader: lessExtract.extract({
                use: ['css-loader?minimize', 'less-loader']
            })
        }, {
            test: /\.scss$/,
            loader: sassExtract.extract({
                use: ['css-loader?minimize', 'sass-loader']
            })
        }
    ]
}

...

plugins: [
    cssExtract,
    lessExtract,
    sassExtract
] > contenthash:它的出现主要是为了解决,让css文件不受js文件的影响。比如foo.css被foo.js引用了,所以它们共用相同的chunkhash值。但这样子是有问题的,如果foo.js修改了代码,css文件就算内容没有任何改变,由于是该模块的 hash 发生了改变,其css文件的hash也会随之改变。 这个时候我们就可以使用contenthash了,保证即使css文件所处的模块里有任何内容的改变,只要 css 文件内容不变,那么它的hash就不会发生变化。

3.拷贝静态文件

const CopyWebpackPlugin = require('copy-webpack-plugin');
    plugins: [
        new CopyWebpackPlugin([{
            from: path.join(__dirname, 'public'),       // 从哪里复制
            to: path.join(__dirname, 'dist', 'public')  // 复制到哪里
    }])
]	

4.打包前先清空输出目录

const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins: [
    new CleanWebpackPlugin([path.join(__dirname, 'dist')])
]

5.压缩js

const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
plugins: [
    new UglifyjsWebpackPlugin()
]	 ### 五、devServer:配置开发服务器

devServer:{
	Open:true,//则自动打开浏览器
	Hot:true,//热模块更新作用
	port:8080        // 开发服务器监听的端口,默认8080
	host:'localhost',// 开发服务器监听的主机地址,默认localhost
	compress:true,   // 开发服务器是否启动gzip等压缩
	contentBase:path.resolve(__dirname,'dist'),// 额外的静态资源目录,不受publicPath影响, 访问path为/ 根路径	    
	publicPath:'/internal/',
	proxy: {
	'/proxy': {
	    target: 'http://your_api_server.com',
	    changeOrigin: true,
	    pathRewrite: {
	        '^/proxy': ''
	   	 }
	}
}
  • devServer 里配置了 hot: true,即修改或模块后,保存会自动更新,webpack会自动添加 HMR(webpack.HotModuleReplacementPlugin) 插件,页面不用刷新呈现最新的效果。所以模块热更新最终还是 HMR 这个插件起的作用。

  • 配置了 publicPath后, url = ‘主机名’ + ‘publicPath配置的’ +’原来的url.path’。这个 其实与 output.publicPath 用法大同小异。
    output.publicPath 是作用于 js, css, img 。而 devServer.publicPath 则作用于请求路径上的。

六、devtool:打包方式

从上到下,打包速度越来越快,开发环境一般用eval-source-map,生产环境自行斟酌咯。毕竟打包越快,打包质量也就越差。还有,不知大家发现没,带eval都不支持生产模式哦。

if (env.prod) {
        config.devtool = 'source-map';

七、resolve:解析

resolve: { //解析模块的可选项  
        // modules: [ ]//模块的查找目录 配置其他的css等文件
        extensions: ["",".js", ".json", ".jsx",".less", ".css"],  //用到文件的扩展名
        alias: { //模快别名列表
            utils: path.resolve(__dirname,'src/utils')
        }
    } #### 1.extensions 指定extension之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配 #### 2.alias 配置别名可以加快webpack查找模块的速度