一、兼容性问题的产生
随着时间的推移,es6及其更高版本的'新版js'语法逐渐普及使用开来。
不像后端一样,只需要升级一台掌握在开发者手中的设备(服务器),就万事大吉。
由于客户端浏览器掌握在用户手中,他们拿老的浏览器访问我们新版的语法,代码自然是运行不起来,也就产生了前端颇为头痛的‘兼容’问题。
二、如何解决
新语法与类
对于新版语法(比如新增的语法:for of 、解构、展开运算等等)和对象(比如Set、Promise等等),这些用babel工具来做处理,就可以让老的浏览器也可以跑新的语法代码。
模块化
然而对于前端的模块化,babel却无能为力,因为模块化可是一项大工程,这个时候webpack就登场了,它是专门用于处理模块化的工具。即es6Module或Amd或Cmd等。
编译
babel和webpack原理本质都是:拿到你的代码,然后加工处理后,你再使用它处理过的文件。这个过程叫做编译。
两者关系
这两者相辅相成,都是处理新版js语法工具。
如果你的代码没有包含模块化,那么无须使用webpack,只用babel即可。
如果你的代码没有包含新语法,只包含模块语法如import等,那么无须使用babel,只需使用webpack即可。
准备
安装node:这两者都依赖于node,所以后边的内容,都假设你全局安装好的node环境。
创建项目demo,目录解构如下:
index.html
src
main.js
package.json //这个使用npm init -y自动生成
三、babel
babel做什么的?
babel提供将新版的js语法及对象特性。让老的浏览器,比如ie8也支持你写的代码的一款工具。
说通俗了就是es6 转换 es5的工具。
安装babel
npm install --save-dev @babel/core @babel/cli
创建babel配置文件
一般会在根目录下创建配置文件babel.config.js
,编译的时候,babel会自动根据配置文件内容智能编译。
// 预设
const presets = [];
// 插件
const plugins = [];
module.exports = { presets,plugins };
插件
你的每一种新语法,对应的就是babel的某一个插件,比如你的箭头函数,对应的是插件包@babel/plugin-transform-arrow-functions
。
如果你的代码中有用到此语法,那么你需要npm install
此插件,并写入plugins的数组中。这样编译的时候,就会调用该插件,对你的代码解析。
预设
预设本质是一个插件集合,比如你写的代码里用到了很多新语法,那么你的配置文件中的plugins就需要写很多,要维护这个插件配置项,很麻烦。
于是预设就出来了。你可以自定义一个预设,但是官方提供的已经够用了,所以一般都用官方的。
一般使用banbel都会用预设,而不用插件,因为它省事。
如果都不设置
如果预设和插件为空,那么执行编译命令时候,那么babel将不会对你的代码做任何处理,输出编译后的代码与你写的一模一样。
编译命令
babel本质上,是它会将你的代码读取,然后根据它的规则编译
生成一个新的文件,然后你再引用这个编译后的文件,就可以兼容老浏览器了,编译命令为
npx babel src --out-dir build
src指的是你需要编译的源文件目录,build是编译后生成的目录,如果没有它会自动创建。
babel处理新语法
哪些是新语法,新语法其实是相对而言的,相对于时间或主流或目前大众浏览器兼容性。一般说新语法,指的是es6及其以后的语法。比如:
箭头函数、类、for of、解构和扩展运算、let和const、函数新参默认值、字符串模板、async和await等等
这些新语法跑在老的浏览器上比如ie8,想都不用想,直接就报错了。
举个例子
比如如下代码:
const getUname = ()=>{
return '你好'
};
分析代码发现用到了两处新语法,const和箭头函数。如果我们用babel编译此代码,需要安装两个对应的解析插件包:
npm install --save-dev @babel/plugin-transform-block-scoping
npm install --save-dev @babel/plugin-transform-arrow-functions
并添加到配置项plugins中:
const presets = [];
const plugins = [
'@babel/plugin-transform-block-scoping',
'@babel/plugin-transform-arrow-functions'
];
module.exports = { presets,plugins };
执行前边说的编译命令后,查看build文件夹下的mian.js文件,代码如下:
var getUname = function () {
return '你好';
};
经过如此手段,再在页面上引入编译后的文件,这代码已经可以再老的浏览器上运行了。
简化配置
也看到了,如果我代码里再有其他新语法,那plugins就会更长了。所以使用预设的方式,即插件集,不再一一手动配置插件项。
安装官方预设:
npm install --save-dev @babel/preset-env
修改配置文件如下:
const presets = [
'@babel/preset-env'
];
const plugins = [];
module.exports = { presets,plugins };
再来编译,发现输出的跟刚才使用插件项编译的结果一样。
插件或预设的配置项
有一个点要注意,这些插件或预设都是可以配置的。
比如,如上预设项@babel/preset-env
我没有给其加配置项,那么默认会编译所有的es6+。
如果你的目标浏览器值考虑chrome某个版本,这样就不划算了。因为会编译好多代码。
这个时候可以添加配置项,为其制定编译的目标浏览器,他就会根据此浏览器当前的兼容性,适当编译,而非全部编译。因为有的代码本身浏览本身已经兼容了某些新语法。
const presets = [
[
'@babel/preset-env',
{ // 配置项
targets: { chrome: "58", ie: "11" }
}
]
];
const plugins = [];
module.exports = { presets,plugins };
babel处理新对象
es6不但扩充了语法,也新增了很多对象,比如:
Promise、Set等等
这些新对象,并非是'语法',所以靠babel编译语法肯定是行不通了,所以babel提供了一个垫片( polyfill)来处理这些新对象。
原理是,引入babel提供的垫片库到全局,这样相当于是在全局(window)注入定义了这些对象。所以就可使用了。
比如我有如下代码
const map = new Map();
map.set('uname','abc')
console.log(map.get('uname'))
通过编译命令编译后,编译过的代码如下:
var map = new Map();
map.set('uname', 'abc');
console.log(map.get('uname'));
可以看到,由于Map是新增的对象,并非语法,所以编译也无法对其转换。这时我们可以通过增加垫片的方式,给当前环境注入这些没有的对象。
安装垫片
npm install --save @babel/polyfill
使用垫片
一般情况下是配合webpack用import的方式引入垫片,但是目前没有学到webpack。所以使用了垫片的另一种引入方式Usage in Browser
在index.html中引入script标签,具体如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>测试</div>
<script src="./node_modules/@babel/polyfill/dist/polyfill.js"></script>
<script src="./src/main.js"></script>
</body>
</html>
这样,即便是ie8,也能支持,打开浏览器,控制台,就可以看到输出了abc。
控制台输入window.Map发现也有了内容,而并非之前的is not defined
babel的其它功能
事实上,babel的野心可不止编译新版js这么简单,它还是支持编译jsx,react、ts等,只要安装了对应的插件或者预设,他就可以编译。
三、webpack
webpack做什么的?
前一章讲babel的时候,我尽量绕开import、export等模块化语法特性,是因为: babel不处理js的模块化。我们还需要一款额外使用模块打包工具。
而webpack恰恰就是这么一款专门处理模块化的库:它是模块打包器,是一种模块化的解决方案。其设计理念,所有资源都是“模块”
它会分析你的项目结构依赖关系,找到Js模块和其它的浏览器不能直接运行的拓展语言如css,TypeScript,并将其转换和打包为合适的格式供浏览器使用。
它只会打包js,其他文件它无法处理,但可以通过合适的loader处理后再给它,它就可以处理了
webpack的核心概念分为 入口(Entry)、加载器(Loader)、插件(Plugins)、出口(Output);
安装webpack
npm install --save-dev webpack webpack-cli
创建webpack配置文件
在 webpack 4 中,可无配置使用,默认入口src>index.js,输出在dist。
然而大多数项目会需要很复杂的设置且在终端手动输入大量命令,这就是为什么 webpack 仍支持配置文件。
在根目录下创建配置文件webpack.config.js
const path = require('path');
module.exports = {
// 编译模式,开发or生产,若生产,代码会被压缩,调试不利
mode: "development",
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
编译命令
webpack和babel一样,都是会编译和生成新代码代码。编译命令为
npx webpack
配置文件里指明配置,无须命令行后加参数。
举个例子
项目如下
index.html
<body>
<div>测试</div>
<script src="./dist/bundle.js"></script>
</body>
src/untils.js
export var getTime = function(){
return new Date();
};
src/index.js
import { getTime } from './utils'
console.log(getTime())
执行编译命令,然后浏览器打开页面就可以看到结果了
编译后的文件在配置文件指定的dist目录的bundle.js。
可以明显看到,它把两个文件合打包并成了一个。
经过webpack处理后,浏览器就支持了你的包含模块化的代码
loader
webpack只能打包js文件,对于其他类型文件如ts、css、less、coffeejs。它是不能直接处理的。
需要指定loader,loader处理完后,再由webpack来打包处理就可以了。
对css模块化支持
如果直接import css引入,不进行配置,会报错的。
webpack 在打包文件的时候,发现了 .css 后缀名的文件.
webpack 本身只认识 .js .json 文件,这个 .css 文件,它不知道该怎么处理.
于是就从配置文件中,是否有 moudle.ruls[x].test=/.css$/的配置.发现没有
于是就报错 You may need an appropriate loader to handle this file type
安装相关loader:
npm install --save-dev style-loader css-loader
在module下增加如下配置:
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
然后新建样式src/index.css
html,body{color: red;}
然后在index.js引入样式:
import './index.css'
console.log('i am hero')
重新编译,刷新页面
可以看到页面上的字已经变红了
用到了两个loader,loader的加载顺序是从右到左:
css-loader
: 以字符串形式读取CSS文件。
style-loader
:获取字符串,解析这些样式并使用<style>
将css-loader内部样式注入html中
plugins
插件比loader更为强大,比如打包优化,压缩,软连接,是对webpack本身的扩展
HtmlWebpackPlugin
每次编译完成后,页面需要手动引入的是编译后的js文件,很麻烦。
而此插件,会自动帮你引入,并生成到编译目录内。
npm install --save-dev html-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const htmlWp = new HtmlWebpackPlugin({
template:'./src/index.html'
});
module.exports = {
mode: "development",
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [htmlWp]
};
loader与plugins区别
1 .loader在模块加载时的预处理文件,运行在打包文件之前
2 . plugins处理loader无法处理的事,plugins在整个编译周期都起作用
常用功能
- 语法既有es6新增特性和对象,又有模块
使用babel-loader
babel会处理es6除了模块外的其他东西
然后webpack自己会处理模块 - 处理ts
npm npm install --save-dev typescript ts-loader
根目录下创建配置文件tsconfig.json
:
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": false,
"module": "commonjs",
"target": "es5",
"allowJs": true
}
}
输出目录、资源映射、是否强制声明类型、编译模块化方案...
- ts中使用es6语法报错
使用es6或更高或操作dom的语法,编辑器会标红,不认识。
需要在tsconfig.js中告诉ts识别、编译额外依赖哪些lib。本来是有默认的,一旦自定义lib,默认就不起作用了,所以要写全一些。
...
"lib": [
"es5",
"dom",
"es2015"
]
...
- 省略后缀名(.js、.ts)
在配置文件的配置对象中设置resolve项
resolve: {
extensions: [".js", ".json",".ts"]
}
- 不使用框架如何实现
npm run dev
的效果
在配置好了插件html-webpack-plugin
基础上,还需要再安装如下工具包
npm install webpack-dev-server
然后在packge.json的script里配置如下即可
"dev": "webpack-dev-server --inline --progress --config webpack.config.js"
标签:babel,现代化,js,语法,编译,webpack,构建,工具,loader
From: https://www.cnblogs.com/dingshaohua/p/17045862.html