webpack详解
Webpack详解
认识webpack
- 从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具
- 打包工具:
- grunt/gulp/webpack/rollup等
- 前端模块化:
- 前面的学习中提到了为什么使用前端模块化,也提到了目前使用前端模块化的一些方案:
- AMD、CMD、CommonJS、ES6
- 学习了webpack之后,我们就不止能使用ES6,webpack可以作为这些方式的底层支撑,并且会将其转化成浏览器能识别的代码,也能将ES6语法转为ES5的语法,处理各模块之间的依赖关系。
- 而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当作模块来使用
- 前面的学习中提到了为什么使用前端模块化,也提到了目前使用前端模块化的一些方案:
- webpack依赖于node环境
- node环境为了支撑执行很多代码,其中必须依赖各种的包,node手动管理这些包很麻烦,所以下载node时会安装npm工具(npm packages manager)。
webpack的安装
-
全局安装webpack(这里先指定版本号3.6.0,因为vue cli2依赖该版本)
-
npm install webpack@3.6.0 -g
-
-
局部安装webpack
-
--save-dev 是开发时依赖,项目打包后不需要继续使用。
-
cd 对应目录 npm install webpack@3.6.0 --save-dev
-
-
为什么全局安装后,还需要局部安装?
- 在终端直接执行webpack命令,使用的是全局安装的webpack
- 当在package.json中定义了scripts脚本时,这个脚本中包含了webpack命令,那么使用的是局部webpack(详见webpack的配置)
webpack的起步
-
新建项目 -> 创建文件夹 -> src 和 dist文件夹
- dist:distribution(发布),将来直接发布这个文件夹即可。
- src:源码
-
cd到01-webpack的起步目录下,执行
webpack ./src/main.js ./dist/bundle.js -
代码使用的是CommonJS的导出导入方式,直接运行代码浏览器不会识别
-
在index.html中,先不引用js文件,当webpack生成后再引用生成的文件即可
<script src="./dist/bundle.js"></script> -
在命令中,只需引入main.js即可,会自动处理引用的文件,并将生成的文件放到dist目录下的bundle.js中
-
-
-
浏览器resource中可以看到:已经成了编译后的代码(本来浏览器可能不识别这些代码,这些编译后的代码就可以作为一个支撑,让浏览器识别)
-
还支持同时使用不同的导出、导入方式:
main.js
import {name, age} from "./info"; const {add, mul} = require('./mathUtils.js') console.log('Hello World'); console.log(add(20, 30)); console.log(mul(20, 30)); console.log(name); console.log(age); info.js(新增,之前的mathUtils.js为CommonJS导出方式)
export const name = 'Elian' export const age = 18 重新执行package命令后,会同时打印结果:
webpack的配置
-
每次执行命令webpack ./src/main.js ./dist/bundle.js很麻烦,所以对webpack进行一些配置
-
首先,一旦当前项目准备用node中的一些东西时,就先在当前项目路径下执行一下命令进行初始化,引入依赖,在当前文件下创建包文件
npm init -
终端中填写packaeg name等信息,注意中文以及符号问题
回车即可
-
这时会在当前项目下创建package.json文件(以下,文件说明)
-
创建webpack.config.js
const path = require('path') // CommonJS语法,导入path module.exports = { // CommonJS语法,导出 entry: './src/main.js', // 入口 output: { // 出口 path: path.resolve(__dirname, 'dist'), // 动态获取绝对路径 __dirname:当前路径 dist:要生成文件的路径 filename: 'bundle.js' } } -
现在只要在terminal中执行
webpack 即可。项目打包重新运行,输出结果
-
-
当命令很长的时候,我们需要配置npm run *命令指向特定命令
-
打开package.json文件,编辑"script"
这样在执行:npm run build时,就会执行webpack命令了
-
这样配置的原因:
-
可能一条命令很长,而我们要在测试时多次执行这条命令,现在无论多长的命令都可以通过npm run *的方式来运行
-
我们配置了全局的webpack,有时候全局的webpack可能时4以上的版本,当前项目可能使用的是3.X的版本,会找错版本导致打包之后出现问题,这时,会在本地进行安装package,之后执行npm run build 就会先在本地找webpack了。而我们要在cmd,terminal窗口中执行webpack命令,它只会在全局去找这个命令。
在项目路径下执行
npm install webpack@3.6.0 --save-dev --save-dev:是指开发时依赖,因为很多时候我们打完包以后,就不用webpack了,所以只需要安装开发时依赖即可。
-
-
执行完后会在package.json中多出:
开发时依赖
-
loader的使用
- loader是webpack中一个非常核心的概念。
- 已经知道webpack用来:
- 处理js之间相关的模块引用,依赖。
- 但是除了js还要加载css,图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。
- 对于webpack本身的能力来说,对于这些转化是不支持的,这时给webpack扩展对应的loader就可以啦
loader使用过程
- 步骤一:通过npm安装需要使用的loader,不同的文件需要不同的loader
- 步骤二:在webpack.config.js中的modules关键字下面进行配置
-
重构代码:
-
在main.js中添加对normal.css的引用,webpack对loader的支持在
里讲的很详细
npm install --save-dev css-loader@2.0.2 -
在webpack.config.js中配置:
const path = require('path') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { // 新增 rules: [ { test: /\.css$/, use: [ 'css-loader' ] } ] } } 打开浏览器发现界面并没有变红,这里说明:css-loader只负责加载文件,不负责渲染样式,要想渲染要是还需加载一个loader
-
安装style-loader
npm install style-loader@0.23.1 --save-dev 配置webpack.config.js
use: [ 'style-loader', 'css-loader' ] -
再次运行npm run build,刷新浏览器,发现背景已经改变
-
注意:loader加载时是从右向左读取的
其他类型文件的loader
-
less文件:
-
引入loader
npm install less-loader@4.1.0 --save-dev less@3.9.0 -
配置webpack.config.js
rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.less$/, use: [{ loader: 'style-loader' // creates style nodes from JS strings }, { loader: 'css-loader' // translates CSS into CommonJS }, { loader: 'less-loader' // compiles Less to CSS }] } ] -
说明:less-loader将文件加载到DOM中,less将文件转成css
special.less
@fontSize: 50px; @fontColor: orange; body { font-size: @fontSize; color: @fontColor; } 并在main.js中引入
-
-
图片:
-
引入loader
npm install --save-dev url-loader@1.1.2 -
配置webpack.config.js
rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.less$/, use: [{ loader: 'style-loader' // creates style nodes from JS strings }, { loader: 'css-loader' // translates CSS into CommonJS }, { loader: 'less-loader' // compiles Less to CSS }] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 14000 } } ] } ] -
说明:options中limit的作用,最小的缓冲区,如果文件大于这个值,必须引入另一个file-loader,如果小于这个值,那么会将图片转成base64,这个file-loader直接下载即可,不需要针对loader进行特别的配置。
npm install file-loader@3.0.1 --save-dev -
但是,之前没有loader时,并且文件大小小于配置中的大小时会转成base64的字符串形式,这个loader会将文件重新命名后加载到dist文件中,这时就要配置一个路径,让这原先的路径能找到dist下的这个文件。
-
配置webpack.config.js中的output
-
添加publicPath路径,这个属性的目的就是为了在url前面加上一个对应的路径。
-
同时,dist下生成的文件是个hash32位的命名,很乱,不知道原图片是什么,这个时候还需对名字进行配置:
-
最终生成的文件:
-
-
-
-
ES6转ES5的babel的loader:
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015 配置webpack.config.js
webpack中配置Vue
引入vue.js
-
后续项目中我们会使用Vuejs进行开发,而且会以特殊的文件来组织vue的组件
-
安装依赖
-
因为后续发布的时候也会使用vue,所以并不是开发时依赖
npm install vue --save
-
-
然后就可以按照之前的方式使用vue了。
// index.html中添加vue挂载的div <div id="app"> {{message}} </div> // main.js中引入vue,并copy我们之前的代码过来 import vue from 'vue'; const app = new Vue({ el: '#app', data: { message: 'Hello World' } }) -
build运行之后发现报错了
这是因为:
vue在构建最终的发布版的时候,构建了两类发布版本:
- runtime-only --在这个版本中不允许有template
- runtime-compiler --在这个版本中允许有template,因为有compiler代码用于编译template
-
当前解决办法
-
在webpack.config.js中添加resolve:
-
-
el和template的区别:
-
正常运行以后,我们来考虑一个问题:
- 如果我们希望将data中的数据显示在界面中,就必须修改index.html
- 如果我们后面自定义了组件,也必须修改index.html来使用组件
- 但是vue作为单页面复用技术,并须希望手动的来频繁的修改,是否可以做到呢?
-
定义template属性:
-
当index.xml中和Vue实例中同时定义了同一个div,就会将template中的div替换到html中的div去
-
对template进行优化
-
第一次优化:
抽取模板
在vue中定义components
在vue的template中引用
-
第二次优化:
在src目录下新建vue文件夹,创建app.js并导出一个default的模板
定义components,引入App
在main.js中导入,并在template中引用
-
第三次优化
-
在vue目录下创建vue componen文件
<template> <div id="app"> <h2 class="message">{{message}}</h2> <button @click="btnClick">按钮</button> <h2>{{name}}</h2> </div> </template> <script> export default { name: "App", data() { return { message: 'Hello World', name: 'Elian' } }, methods: { btnClick() { console.log('按钮被点击了'); } } } </script> <style scoped> .message { color: orange; } </style> -
删除刚刚创建的app.js即可,并在main.js中引用即可
import Vue from 'vue'; import App from './vue/app'; // 新的引用 new Vue({ el: '#app', template: `<App/>`, components: { App } }) 这时进行build会报错,原因是没有处理.vue的loader
-
-
安装loader(开发必用)
-
安装:vue-loader 和 vue-tempalte-compiler[vue-loader最好用13.0.0以上的版本,避免安装其他插件]
npm install --save-dev vue-loader vue-template-compiler -
配置:
{ test: /\.vue$/, use: ['vue-loader'] } -
vue-loader在14以上的版本中会出现需要另外安装插件的问题
-
plugin的使用
认识plugin
- plugin是什么?
- plugin(插件),通常是对现有 的架构进行扩展
- webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等。
- loader和plugin的区别?
- loader主要用于转换某些类型的模块,他是一个转换器
- plugin是插件,它是对webpack本身的扩展,是一个扩展器
- plugin的使用过程:
- 步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
- 步骤二:在webpack.config.js中的plugins中配置插件
让webpack更加好用的plugins
-
添加版权的Plugin
-
这个插件属于内置插件,不需要再install,直接进行引用,并添加plugins属性并添加要输入版权的名称
-
-
打包html的plugin
-
目前我们的index.html文件是存放在项目的根目录下的。
- 我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。
- 所以我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件
-
HtmlWebpackPlugin插件可以为我们:
- 自动生成一个index.html文件(可以指定模板来生成)
- 将打包的js文件,自动通过script标签插入到body中
-
安装HtmlWebpackPlugin插件
npm install html-webpack-plugin --save-dev -
使用插件,修改webpack.config.js文件中plugins部分的内容如下:
-
这里的template表示根据什么模板来生成index.html
-
另外,我们休要删除之前在output中添加的publicPath属性删除,否则插入的script标签中的src可能会有问题。
-
index.html中,还是需要一个id为app的div,但是不用再引入bundle.js了,会自动引入dist下的bundle.js
-
-
-
js压缩的Plugin
-
在项目发布之前,我们必然需要对js等文件进行压缩处理
-
这里,我们就对打包的js文件进行压缩
-
我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
-
-
修改webpack.config.js文件,使用插件:
const uglifyJsPlugin = require('uglifyjs-webpack-plugin') ---- plugins: [ ... new uglifyJsPlugin() ]
-
搭建本地服务器
-
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
-
不过它是一个单独的模块,在webpack中使用之前要先安装他:
npm install --save-dev webpack-dev-server@2.9.1 -
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
- cnotentBase:为那一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
- port:端口号
- inline:页面实时刷新
- historyApiFallback:在SPA页面中,依赖HTML5的history模式
-
webpack.config.js文件和package.json配置如下:
-
执行
npm run serve
抽离webpack.config.js
-
为什么要抽离?
-
像我们最后一个配置devServer,只有在开发的时候才用到,真正发布项目后,是不会使用这个devServer的,这时我们就要配置一个开发时运行的和一个发布时运行的。
-
项目下创建src的同级目录build文件夹
-
安装webpack-merge
npm install webpack-merge@4.1.5 --save-dev -
创建base.config.js、dev.config.js、prod.config.js文件,将公共的配置放在base.config.js中,而开发时的配置放在dev.config.js中,生产时的配置文件放在prod.config.js中。
base.config.js
const path = require('path') const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, '../dist'), // 修改路径为../dist filename: 'bundle.js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.less$/, use: [{ loader: 'style-loader' // creates style nodes from JS strings }, { loader: 'css-loader' // translates CSS into CommonJS }, { loader: 'less-loader' // compiles Less to CSS }] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 1400, name: 'img/[name].[hash:8].[ext]' } } ] }, { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } } }, { test: /\.vue$/, use: ['vue-loader'] } ] }, resolve: { extensions: ['.js','.css','.vue'], alias: { 'vue$': 'vue/dist/vue.esm.js' } }, plugins: [ new webpack.BannerPlugin('最终版权归Elian所有'), new HtmlWebpackPlugin({ template: `index.html` }) ] } dev.config.js
const baseConfig = require('./base.config') const webpackMerge = require('webpack-merge') module.exports = webpackMerge(baseConfig, { devServer: { contentBase: '../dist', // 要监听的文件夹 inline: true // 实时监听 } }) prod.config.js
const baseConfig = require('./base.config') const uglifyJsPlugin = require('uglifyjs-webpack-plugin'); const webpackMerge = require('webpack-merge') module.exports = webpackMerge(baseConfig, { plugins: [ new uglifyJsPlugin() ] }) -
修改base.config.js下的path路径为../dist,修改package.json文件,添加build,修改serve
-
-
总结:
创建一个简单的模板:
-
目录结构:
-
base.config.js代码:
const path = require('path') // node语法,导入path const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // CommonJS语法,导出 entry: './src/main', output: { path: path.resolve(__dirname, '../dist'), // 动态获取绝对路径 __dirname:当前路径 dist:要生成文件的路径 filename: 'bundle.js' /*指定文件去dist根下找,如果把index.html放到dist下,就是绝对路径 * 不需要配置这个了*/ /*publicPath: 'dist/'*/ }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.less$/, use: [{ loader: 'style-loader' // creates style nodes from JS strings }, { loader: 'css-loader' // translates CSS into CommonJS }, { loader: 'less-loader' // compiles Less to CSS }] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'url-loader', options: { // 当加载的图片,小于limit时,会将图片编译成base64字符串形式 // 当加载的图片,大于limit时,需要使用file-loader模块进行加载 limit: 1400, // [name]:原名 [hash:8]取hash8位 [ext]继承原格式 name: 'img/[name].[hash:8].[ext]' } } ] }, { test: /\.js$/, // 排除node_modules等文件夹下的文件转换 exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } } }, { test: /\.vue$/, use: ['vue-loader'] } ] }, // 目的是为了在执行 import Vue from 'vue' 时 // 先来看有没有将vue指向别的文件 // 这时就不会去找默认的文件了,而是来找这个指定的文件 resolve: { extensions: ['.js','.css','.vue'], alias: { 'vue$': 'vue/dist/vue.esm.js' } }, plugins: [ new webpack.BannerPlugin('最终版权归Elian所有'), new HtmlWebpackPlugin({ template: `index.html` }) ] } -
dev.config.js代码
const baseConfig = require('./base.config') const webpackMerge = require('webpack-merge') module.exports = webpackMerge(baseConfig, { devServer: { contentBase: '../dist', // 要监听的文件夹 inline: true // 实时监听 } }) -
prod.config.js代码
const baseConfig = require('./base.config') const uglifyJsPlugin = require('uglifyjs-webpack-plugin'); const webpackMerge = require('webpack-merge') module.exports = webpackMerge(baseConfig, { plugins: [ new uglifyJsPlugin() ] }) -
index.html模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> </div> </body> </html> -
App.vue代码(可以等完成npm install之后再创建)
<template> <div id="app"> <h2 class="message">{{message}}</h2> <button @click="btnClick">按钮</button> <h2>{{name}}</h2> <h2>{{name}}</h2> </div> </template> <script> export default { name: "App", data() { return { message: 'Hello World', name: 'Elian' } }, methods: { btnClick() { console.log('按钮被点击了'); } } } </script> <style scoped> .message { color: orange; } </style> -
main.js代码
//使用vue进行开发 import Vue from 'vue'; import App from './vue/App.vue' new Vue({ el: '#app', template: `<App/>`, components: { App } }) -
package.json代码
{ "name": "template", "version": "1.0.0", "description": "vue project develop template", "main": "webpack.config.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config ./build/prod.config.js", "dev": "webpack-dev-server --open --config ./build/dev.config.js", "webpack": "webpack --version" }, "author": "Elian", "devDependencies": { "babel-core": "^6.26.3", "babel-loader": "^7.1.5", "babel-preset-es2015": "^6.24.1", "css-loader": "^2.0.2", "file-loader": "^3.0.1", "html-webpack-plugin": "^3.2.0", "less": "^3.9.0", "less-loader": "^4.1.0", "style-loader": "^0.23.1", "uglifyjs-webpack-plugin": "^1.1.1", "url-loader": "^1.1.2", "vue-loader": "^13.0.0", "vue-template-compiler": "^2.5.21", "webpack": "^3.6.0", "webpack-dev-server": "^2.9.1", "webpack-merge": "^4.1.5" }, "dependencies": { "vue": "^2.5.21" } } -
所有配置完以后,在当前目录下cmd执行以下命令
npm init npm install --legacy-peer-deps 然后执行步骤6 npm run dev/build
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)