Webpack核心概念解析

3,308 阅读3分钟

原文链接:banggan.github.io/2019/05/09/…

Webpack核心概念解析

终于忙完了论文,可以愉快的开始学习了,重拾起重学前端、webpack以及Vue的源码解读作为入职前的复习吧。整个webpack系列将分成五个大的部分进行,以webpack4.0为文档进行解读,从简单的概念解读到最后的实现。 整个知识点涉及范围:

image

loader

使用loader来预处理文件,把不同的静态资源(模块的结尾不是js的模块)打包成js文件

loader打包静态资源

打包图片

  • 安装使用file-loader实现:npm install file-loader -D
  • 在webpack.config.js中添加loader的配置
module.exports = {
    //打包项目的入口文件
    entry: './src/index.js',
    module:{
        rules:[{
            test:/\.(jpg|png|gif)$/,//打包以jpg、png、gif结尾的所有图片文件
            use:{
                loader:'file-loader',
                options:{//placeholder 占位符 
                    name:'[name]_[hash].[ext]',//保持原图片的名字+hash值和后缀,主要单引号
                    outputPath:'image/'//打包图片的位置
                }
            }
        }]
    }
}

打包图片成base64格式

url-loader基本能实现file-loader的打包功能,适用于小图片的打包

  • 好处:图片打包成js文件,不用加载图片的地址,页面快速显示
  • 坏处:图片过大导致js文件过大

所以,当图片的大小小于limit值时会把图片打包成base64格式,大于limit值则按照file-loader打包成图片文件

  • 安装使用url-loader实现:npm install url-loader -D
  • 在webpack.config.js中添加loader的配置
module.exports = {
    module:{
        rules:[{//打包以jpg、png、gif结尾的所有图片文件
            test:/\.(jpg|png|gif)$/,
            use:{
                loader:'url-loader',
                options:{//placeholder 占位符 
                name:'[name]_[hash].[ext]',//保持原图片的名字+hash值和后缀,主要单引号
                outputPath:'image/',//打包图片的位置
                limit:2048
            }
        }]
    }
}

打包样式css文件

需要使用css-loader、style-loader

  • css-loader:分析几个css文件的关系,合并css文件
  • style-loader:将css-loader合并的css内容挂载在页面的head部分

实现方式:

  • 安装loader实现:npm install css-loader style-loader -D
  • 在webpack.config.js中添加loader的配置
module.exports = {
    module: {
        rules: [{//打包css文件
            test:/\.css$/,
            use:['style-loader','css-loader']
        }]
    }
}

打包样式scss文件

需要使用sass-loader、node-sass

  • 安装loader实现:npm install sass-loader node-sass -D
  • 在webpack.config.js中添加loader的配置
module.exports = {
    module: {
        rules: [{
            test: /\.scss$/,
            use:['style-loader','css-loader','sass-loader'] 
        }]
    }
};

在配置中,有三个loader,执行顺序是从下到上,从右到左。在打包scss文件时,首先执行sass-loader:对sass翻译成css文件,在挂载到css-loader,最后style-loader.

为样式添加不同浏览器的前缀

为了兼容不同的浏览器,在写样式的时候需要加上适用不同浏览器的前缀,如-o、-webkit、-moz等

-安装loader实现:npm install postcss-loader autoprefixer -D -在根目录创建postcss.config.js

moudle.exports ={
	plugins:[
        require('autoprefixer')
	]
}
  • 在webpack.config.js中添加loader的配置
module.exports = {
    module: {
        rules: [{
            test:/\.scss$/,
            use:[
            'style-loader',
            'css-loader',
            'sass-loader',
            'postcss-loader']
        }]
    }
}

css-loader添加不同的配置

css模块化打包
  • 场景:在文件引入的scss不仅影响当前的文件,还影响当前文件引入的其他js文件,造成样式冲突
  • 实现:css只在当前模块类有效,在配置中添加modules:true开启css的模块化打包,在引入的时候注意区分
scss文件的嵌套引用
  • 场景:scss文件通过import引入其他scss文件,导致打包的时候引入的scss文件打包错误

  • 实现:importLoader:2

  • 在webpack.config.js中添加loader的配置

module:{
        rules:[{//打包scss文件
            test:/\.scss$/,
            use:[
            'style-loader',
            {
                loader:'css-loader',
                options:{
                    importLoaders:2,//index.scss中通过import引入其他的scss文件,引入的scss文件在打包的时候也将依次经过所有的loader
                    modules:true 
                }
            },
            'sass-loader',
            'postcss-loader']
        }]
    }

打包字体图标文件

阿里巴巴矢量图标库中,把需要的字体图标下载到本地,解压。将iconfont.eot iconfont.svg iconfont.ttf iconfont.woff 四种图片文件放入到项目中,在src中新建一个放字体图标的文件夹font。将iconfont.css文件拷贝到项目中,修改对应字体的引用路径。

  • 需要安装 file-loader:npm i file-loader -D
  • 在webpack.config.js中添加loader的配置
module.exports = {
    ...
    module: {
        rules: [{
            test: /\.(eot|ttf|svg|woff)$/,
            use:{
                loader:'file-loader'
            }
        },
            ]
        }]
    }
}

打包数据文件

如遇到json、scv、xml文件需要打包时,使用csv-loader 和 xml-loader实现。

  • 安装:npm install csv-loader xml-loader -D
  • 在webpack.config.js中添加loader的配置
  module.exports = {
    module: {
      rules: [{
         test: /\.(csv|tsv)$/,
         use: [
           'csv-loader'
         ]
       },
       {
         test: /\.xml$/,
         use: [
           'xml-loader'
         ]
       }]
    }
  }

plugins

loaders可以将各个类型的静态资源打包成webpack能处理的模块,而plugins有更强大的功能。它可以从打包优化和压缩,一直到重新定义环境中的变量。

plugin可以在webpack运行到某一个时刻,自动完成一些事情。

自动生成html文件,并引入打包生成的js文件到生成的html文件中

  • 安装使用HtmlWebpackPlugin实现:npm install html-webpack-plugin -D
  • 在webpack.config.js中添加loader的配置

在src中创建一个html的模板,在HtmlWebpackPlugin的配置中引入该模板,打包后生成和模板类似的html文件并引入打包的js文件。

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

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
    plugins: [new HtmlWebpackPlugin({
        template: 'src/index.html' 
    })]
}

自动清除上一次打包的dist文件

先删除上一次打包的dist文件,再执行打包

  • 安装使用CleanWebpackPlugin 实现:npm install clean-webpack-plugin -D
  • 在webpack.config.js中添加loader的配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
    plugins: [
        new HtmlWebpackPlugin({
        template: 'src/index.html' 
    }),
        new CleanWebpackPlugin(['dist']), // 在打包之前,可以删除dist文件夹下的所有内容
    ]
}

Entry与Output的基础配置

  • 需求:打包多个入口文件,对应的在出口的配置中注意命名filename避免出口文件名字冲突----使用占位符)来确保每个文件具有唯一的名称
  • 需求:打包后的文件,作为后端的接口文件,静态资源需要上传到cdn,在output中进行配置,提前加入cdn的地址。在编译时,如果不知道publicPath的地址,可以留空,在入口起点文件运行时动态设置。__webpack_public_path__ = myRuntimePublicPath
module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js',
		sub: './src/index.js'
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), new CleanWebpackPlugin(['dist'])],
	output: {
		publicPath: 'http://cdn.com.cn', //加入cdn地址
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}

SourceMap的配置

SourceMap是一个映射关系,打包文件和源文件的映射关系,用于开发者的调试。

在devtool中进行设置:devtool: 'source-map'打包速度会降低,在dist里面会有map映射文件

常用设置devtool说明:

  • none:在开发者模式下,默认开启sourcemap,将其关闭
  • inline前缀:不单独生成map文件,把对应的map文件以base64的形式直接打包到js文件。
  • cheap前缀:sourcemap和打包后的js同行显示,并没有映射到列忽略源自 loader 的 source。 map,并且仅显示转译后的代码,所以打包速度相对来说较快。代码出错提示不用精确显示第几行的第几个字符出错,只显示第几行出错,会提高一些性能,
  • moudle前缀:不仅映射业务代码,还会包括loader、第三方模块的错误。
  • eval前缀:打包速度最快。

development环境推荐使用: devtool: 'cheap-module-eval-source-map'

production环境推荐使用: devtool: 'cheap-module-source-map'

使用WebpackDevServer提升开发效率

场景:每次在src里编写完代码都需要手动重新运行 npm run bundle,如何自动解决?

-安装:npm install webpack-dev-server –D

  • devServer参数说明
  1. contentBase :配置开发服务运行时的文件根目录
  2. open :自动打开浏览器
  3. host:开发服务器监听的主机地址
  4. compress :开发服务器是否启动gzip等压缩
  5. port:开发服务器监听的端口
  • 在 webpack.config.js 中,加 devServer
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080
		proxy:{//配置跨域,访问的域名会被代理到本地的3000端口
      	'/api': 'http://localhost:3000'
    	}
	}
}
  • 在 package.json 中配置
{
  "name": "banggan",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",
    "start": "webpack-dev-server",
  },
}

如何实现自己写一个类似webpackdevserver的工具

  • 在package.json 中配置 创建一个新的指令,npm run server运行自己写的类似webpackdevserver的工具
{
  "name": "banggan",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",
    "start": "webpack-dev-server",
	"server" : "node server.js" 
  },
}
  • 安装express:npm install express webpack-dev-middleware -D
  • 在根目录创建一个server.js
  • 在node中直接使用webpack:webpack(config)
const express = require('express'); //引入express
const webpack = require('webpack');//引入webpack
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');//引入配置文件

const complier = webpack(config); //编译一次吗,打包一次代码
const app = express();//创建express实例

app.use(webpackDevMiddleware(complier, {}));

app.listen(3000, () => {//监听3000端口
	console.log('server is running');
});

Hot Module Replacement热模块更新

  • 场景:在程序运行中。替换、添加、替换某个模块,不需要重新加载整个页面,实现交互时更新。

  • 在 webpack.config.js 中,添加配置

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,//开启热更新功能
		hotOnly: true//如果html功能没有实现,也不让浏览器刷新
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()//使用热模块插件
	],
}
  • 在main.js文件中,使用 HotModuleReplacementPlugin 启用模块的热替换功能。接口暴露在moudle.hot属性下面
//如果模块启用了HMR,就可以用 module.hot.accept(),监听模块的更新。
if (module.hot) {
  module.hot.accept('./library.js', function() {
    // 使用更新过的 library 模块执行某些操作...
  })
}

//拒绝给定依赖模块的更新,使用 'decline' 方法强制更新失败。
module.hot.decline(
  dependencies // 可以是一个字符串或字符串数组
)

注意:引入css文件时,用框架Vue,React 时,不需要写 module.hot.accept(),因为在使用css-loader,vue-loader,babel-preset时,均配置好了HMR,不需要自己重新写 如果文件没有内置HMR,需要自己手动写监听模块更新代码

结合Bable处理ES6语法

  • 场景:代码中含有ES6/ES7的代码,为了让低版本的浏览器兼容代码,需要使用Bable进行转换
  • 安装:Bable官网
//preset-env语法转换
npm install babel-loader @babel/core @babel/preset-env -D
//兼容低版本浏览器的语法,函数的补充
npm install --save @babel/polyfill

  • 在 webpack.config.js 中,添加配置
module: {
  rules: [
    {
        test: /\.js$/,
     	exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
     	loader: "babel-loader" ,
        options:{
            "presets": [["@babel/preset-env",{
                targets: {
                    edge: "17",
                    firefox: "60",
                    chrome: "67",
                    safari: "11.1",
                  },//运行在大于**版本的浏览器上,,已经支持es6的高浏览器不需要转换为es5
                useBuiltIns:'usage' //按需添加polyfill,把业务代码中的新语法新函数都转成低版本浏览器兼容的
            }]]
        }
    }
  ]
}

  • 在src目录下的index.js中顶部位置导入import "@babel/polyfill";

注意如果不是打包业务代码,而是写的类库、或者z组件库的时候不能使用@babel/polyfill实现,因为会导致声明的变量变成全局变量,污染全局环境。使用plugin-transform-runtime实现

  • 安装插件
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2

  • 在 webpack.config.js 中,添加配置
module: {
  rules: [
    {
        test: /\.js$/,
     	exclude: /node_modules/,
     	loader: "babel-loader" ,
        options:{
            "plugins": [["@babel/plugin-transform-runtime",{
                "corejs": 2,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
            }]]
        }
    }
  ]
}

由于babel配置的内容较多,官网推荐在根目录下创建属于babel的配置文件.babelrc文件

由于babel需要配置的内容非常多,我们需要在项目根目录下创建一个 .babelrc 文件。 就不需要在 webpack.config.js 中写 babel 的配置了。 在 .babelrc 中:

{
	"plugins": [["@babel/plugin-transform-runtime", {
		"corejs": 2,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
	}]]
}

总结

  • 在webpack.config.js中
module.exports = {
	mode: 'development', //开发环境进行打包,打包的代码不会压缩
	devtool: 'cheap-module-eval-source-map',//不带列信息,只对业务代码进行sourcemap的生成 
	entry: {//配置入口文件
		main: './src/index.js'
	},
	devServer: {//配置webpack,开发环境的调试
		contentBase: './dist',//启动服务器的目录
		open: true,//打开新的端口号8080
		port: 8080,
		hot: true,//打开热替换功能
		hotOnly: true
	},
	module: {//对不同的文件进行打包规则
		rules: [{ 
			test: /\.js$/, //对js文件的babel-loader打包,其配置在.babelrc文件中
			exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,//对图片进行打包
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240//小于10240以base64的形式进行打包
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,//字体文件的打包
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,//scss文件的打包,先用postcss-loader,在用sass-loader进行解析,最后css-loader进行挂载
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,//css文件的打包,没有sass-loader的解析
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),//自动清空上一次打包
		new webpack.HotModuleReplacementPlugin()//热替换插件
	],
	output: {//出口文件
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}