webpack 运行的两种方式
安装: npm installwebpackwebpack-cli--save-dev
方式一:命令行安装
建立 webpack.config.js
const path = require('path');
module.exports = {
entry:'./src/index.js',
output:{
filename:'bundle.js', // 打包出的结果文件
path:path.resolve(__dirname,'dist') // 打包到dist目录下
}
}
运行命令:
"scripts": {
"build": "webpack --config ./webpack.config.js",
"dev": "webpack" // 不写配置文件,默认找当前工作目录下的配置文件
}
注意:wepback 不写配置文件,默认会读取当前工作目录的webpack.config.js
方式二:node 运行的方式
const path = require('path');
var webpack = require('webpack');
var options = {
mode: 'development',
context: process.cwd(),
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {},
plugins: [
new HtmlWebpackPlugin({
template: './dist/index.html', //指定模板文件
filename: 'index.html', //产出后的文件名
inject: false,
hash: true, //为了避免缓存,可以在产出的资源后面添加hash值
}),
],
devServer: {
port: 8080
}
}
var compiler = webpack(options);
命令行启动的适合一个工程,node 运行的方式适合做成一个脚手架。
webpack-dev-server(热更新)
作用: 配置开发服务器,可以在实现在内存中打包,并且自动启动服务
npm installwebpack-dev-server--save-dev
常见使用方式
命令行方式:
"scripts": {
"build": "webpack --env.production --config ./build/webpack.base",
"dev": "webpack-dev-server --env.development --config ./build/webpack.base"
}
nodejs运行
var server = new WebpackDevServer(compiler);
server.listen(8080);
常见的配置:
devServer: {
contentBase: "./build/", //监听代码变化自动提交并刷新网页
host: '0.0.0.0',
port: 8080,
open:'true',
disableHostCheck: true,
proxy: { //配置代理
'/web/webApi': {
target: proxyUrl,
secure: false,
changeOrigin: true,
pathRewrite: {'/web/wx' : ''}
}
}
}
HMR(热模块替换)
启动hmr
devServer: {
contentBase: "./dist",
open: true,
hot:true, //即便便HMR不不⽣生效,浏览器器也不不⾃自动刷新,就开启hotOnly hotOnly:true
},
插件添加
plugins: [
new webpack.HotModuleReplacementPlugin()
],
注意:启动hmr后,css抽离会不生效,还不支持,contenthash,和chunkhash
处理理js模块HMR
需要使⽤module.hot.accept来观察模块更新 从而更新
if (module.hot) {
module.hot.accept("./b", function() {
document.body.removeChild(document.getElementById("number"));
number();
});
}
- 如果不能自动处理,感觉作用就不大。
html-webpack-plugin(页面开发)
单页面打包
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins:[
new HtmlWebpackPlugin({
filename:'index.html', // 打包出来的文件名
template:path.resolve(__dirname,'../public/index.html'),
hash:true, // 在引用资源的后面增加hash戳
minify:{
removeAttributeQuotes:true // 删除属性双引号
}
})
]
注意:上面这种方式会将entry所有的js文件都导入的html中。
多页面打包
多入口需要配置多个entry
entry:{
jquery:['jquery'], // 打包jquery
entry1:path.resolve(__dirname,'../src/entry-1.js'),
entry2:path.resolve(__dirname,'../src/entry-2.js')
},
output:{
filename:'[name].js',
path:path.resolve(__dirname,'../dist')
},
产生多个Html文件
new HtmlWebpackPlugin({
filename:'index.html',
template:path.resolve(__dirname,'../public/template.html'),
hash:true,
minify:{
removeAttributeQuotes:true
},
chunks:['jquery','entry1'], // 引入的chunk 有jquery,entry
}),
new HtmlWebpackPlugin({
filename:'login.html',
template:path.resolve(__dirname,'../public/template.html'),
hash:true,
minify:{
removeAttributeQuotes:true
},
inject:false, // inject 为false表示不注入js文件
chunksSortMode:'manual', // 手动配置代码块顺序
chunks:['entry2','jquery']
})
以上的方式不是很优雅,每次都需要手动添加HtmlPlugin应该动态产生html文件,像这样:
let htmlPlugins = [
{
entry: "entry1",
html: "index.html"
},
{
entry: "entry2",
html: "login.html"
}
].map(
item =>
new HtmlWebpackPlugin({
filename: item.html,
template: path.resolve(__dirname, "../public/template.html"),
hash: true,
minify: {
removeAttributeQuotes: true
},
chunks: ["jquery", item.entry]
})
);
plugins: [...htmlPlugins]
处理CSS文件
解析css样式
我们在js文件中引入css样式!
import './index.css';
再次执行打包时,会提示css无法解析
ERROR in ./src/index.css 1:4
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
安装loader
npm install style-loader css-loader --save-dev
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}
抽离样式文件
默认只在打包时进行样式抽离
module.exports = env => {
let isDev = env.development;
const base = {/*source...*/}
if (isDev) {
return merge(base, dev);
} else {
return merge(base, prod);
}
};
安装抽离插件
npm install mini-css-extract-plugin --save-dev
配置抽离插件
{
test: /\.css$/,
use: [
!isDev && MiniCssExtractPlugin.loader,
isDev && 'style-loader',
"css-loader"
].filter(Boolean)
}
!isDev && new MiniCssExtractPlugin({
filename: "css/[name].css"
})
最终文件配置贴一下:
const path = require("path");
const dev = require("./webpack.dev");
const prod = require("./webpack.prod");
const merge = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = env => {
let isDev = env.development;
const base = {
entry: "./src/index.js",
output: {
filename: "[name].js",
path: path.resolve(__dirname, "../dist")
},
module: {
rules: [
{
test: /\.css$/,
use: [
!isDev && MiniCssExtractPlugin.loader,
isDev && 'style-loader',
"css-loader"
].filter(Boolean)
}
]
},
plugins:[
!isDev && new MiniCssExtractPlugin({
filename: "css/[name].css"
}),
new HtmlWebpackPlugin({
filename: "index.html",
template: path.resolve(__dirname, "../public/template.html"),
hash: true,
minify: {
removeAttributeQuotes: true
}
}),
].filter(Boolean)
};
if (isDev) {
return merge(base, dev);
} else {
return merge(base, prod);
}
};
css预处理器
不同的css预处理器要安装不同的loader来进行解析
- sass: sass-loader node-sass
- less: less-loader less
- stylus: stylus-loader stylus
使用sass
{
test:/\.scss$/,
use:[
!isDev && MiniCssExtractPlugin.loader,
isDev && 'style-loader',
"css-loader",
"sass-loader"
].filter(Boolean)
}
在css文件中可能会使用@import语法引用css文件,被引用的css文件中可能还会导入scss
{
test: /\.css$/,
use: [
!isDev && MiniCssExtractPlugin.loader,
isDev && 'style-loader',
{
loader:"css-loader",
options:{
importLoaders:1 // 引入的文件需要调用sass-loader来处理
}
},
"sass-loader"
].filter(Boolean)
},
处理样式前缀
使用postcss-loader增加样式前缀
npm install postcss-loader postcss autoprefixer --save-dev
在处理css前先增加前缀
{
test: /\.css$/,
use: [
!isDev && MiniCssExtractPlugin.loader,
isDev && 'style-loader',
{
loader:"postcss-loader",
options:{
plugins:[require('autoprefixer')]
}
},
"postcss-loader",
"sass-loader"
].filter(Boolean)
},
或者也可以创建postcss的配置文件postcss.config.js
module.exports = {
plugins:[
require('autoprefixer')
]
}
可以配置浏览器的兼容性范围 .browserslistrc
cover 99.5%
css压缩
在生产环境下我们需要压缩css文件,配置minimizer选项,安装压缩插件
npm i optimize-css-assets-webpack-plugin terser-webpack-plugin --save-dev
在webpack.prod.js文件中配置压缩
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
optimization:{
minimizer:[new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
}
文件指纹
- Hash整个项目的hash值
- chunkhash 根据入口产生hash值
- contentHash 根据每个文件的内容产生的hash值
我们可以合理的使用hash戳,进行文件的缓存
!isDev && new MiniCssExtractPlugin({
filename: "css/[name].[contentHash].css"
})
处理图片
处理引用的图片
import logo from './webpack.png';
let img = document.createElement('img');
img.src = logo;
document.body.appendChild(img);
使用file-loader,会将图片进行打包,并将打包后的路径返回
{
test:/\.jpe?g|png|gif/,
use:{
loader:'file-loader',
options:{
name:`img/[name].[ext]`
}
}
}
处理icon
二进制文件也是使用file-loader来打包
{
test:/woff|ttf|eot|svg|otf/,
use:{
loader:'file-loader'
}
}
转化成base64
使用url-loader将满足条件的图片转化成base64,不满足条件的url-loader会自动调用file-loader来进行处理
{
test:/\.jpe?g|png|gif/,
use:{
loader:'url-loader',
options:{
limit: 1,
esModule: false,
// name:`img/[name].[ext]`,
name: function(file) {
return file;
}
}
}
}
处理JS模块
将es6代码编译成es5代码
代码的转化工作要交给babel来处理
npm install @babel/core @babel/preset-env babel-loader --save-dev
@babel/core是babel中的核心模块,@babel/preset-env 的作用是es6转化es5插件的插件集合,babel-loader是webpack和loader的桥梁。
const sum = (a, b) => {
return a + b;
};
增加babel的配置文件 .babelrc
{
"presets": [
["@babel/preset-env"]
]
}
配置loader
module: {
rules: [{ test: /\.js$/, use: "babel-loader" }]
},
现在打包已经可以成功的将es6语法转化成es5语法!
解析装饰器
npm i @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators --save-dev
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties",{"loose":true}]
]
legacy:true表示继续使用装饰器装饰器,loose为false时会采用Object.defineProperty定义属性
- Plugin会运行在Preset之前
- Plugin 会从第一个开始顺序执行,Preset则是相反的
polyfill
使用@babel/polyfill,修改配置如下:
module.exports = {
entry: ['@babel/polyfill', './src/index.js'],
module: {
rules: [{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
"presets": [
[
"@babel/preset-env",
{
useBuiltIns: "entry"
}
]
],
"plugins":
}
},
}]
}
}
不管三七二十一,将兼容包全部导入进来就可以兼容低版本了。
缺点:
- 体积增加到了470KiB, 压缩后 87.2 KiB,而原来只有 345 bytes,增加了好几百倍。
优化
还好babel7以后支持了按需引入。可以减少兼容包的体积,他只要的实现逻辑是: 遍历你的代码,找到需要引入的模块。从而减少打包体积。
配置:
"presets": [
[
"@babel/preset-env",
{
// debug:true, // 打开debug
useBuiltIns: "usage", // 按需引入
corejs: {
version: 3,
proposals: true
},
targets: { // 支持兼容的版本
android: '4.2',
ios: '9'
}
}
]
经过编译和打包后: 体积是114 KiB, 压缩体积是 19.8 KiB。体积小了许多。是不是已经很完美了!!!但是也有缺点。
缺点: 这种方式会在全局添加方法,和直接修改原型。 这样子会造成全局污染,如果别人也自定义扩展了同样的方法,则会出现相互覆盖的问题。
@babel/plugin-transform-runtime
此插件可以解决全局污染的问题。
更改配置
"plugins": [
[
'@babel/plugin-transform-runtime',{
corejs:3,
helpers: true,
regenerator: true
}
]
]
代码体积: 157 KiB,压缩后体积:26.7 KiB。 通过对比可知,体积稍微大了一点点。那么打包后的代码有什么区别了。我们一起来看一下。
没有配置transform-runtime 的编译代码
配置transform-runtime 之后的编译代码
我们发现 transform-runtime 把我们写的代码都改变了。进行了重新包装,这样子就不会有全局污染了。
比较
| 体积 | 压缩后体积 | 结论 | |
|---|---|---|---|
| 使用全量引入,@babel/polyfill | 470KiB | 87.2 KiB | 最好不要使用 |
| 按需引入 | 114 KiB | 19.8 KiB | 在项目开发中使用比较合适 |
| transform-runtime | 157 KiB | 26.7 KiB | 在写库的时候比较合适 |
参考资料 弄懂babel 配置
校验css,js
校验js
标准配置
- 建议制定团队的eslint规范
- 基于eslint:recommend配置进行改进
- 发现代码错误的规则尽可能多的开启
- 帮助保持团队的代码风格统一而不要限制开发体验
npm install eslint eslint-loader babel-eslint --D
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [path.resolve(root, 'src')],
options: {
formatter: require('eslint-friendly-formatter'),
fix: true
}
}
注意事项: eslint-friendly-formatter:友好提示; enforce:“pre”; 这个属性必须加,不然会有问题,这个属性的主要功能是让这个loader 在所有loader 执行之前执行。
.eslintrc.js
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true
},
plugins: [
"html"
],
settings: {
"html/html-extensions": [".html"],
"html/indent": "0", // code should start at the beginning of the line (no initial indentation).
"html/indent": "+4", // indentation is the <script> indentation plus two spaces.
"html/indent": "tab", // indentation is one tab at the beginning of the line.
"html/report-bad-indent": "error",
"html/javascript-mime-types": ["text/javascript", "text/jsx"]
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
// http://eslint.cn/docs/rules/
// https://cn.eslint.org/docs/user-guide/configuring
rules: {
//vue
"vue/no-use-v-if-with-v-for": 2, //禁止在与v-for相同的元素上使用v-if
//un vue
'indent': [2, 4, {
'SwitchCase': 2 //case 子句将相对于 switch 语句缩进 4 个空格
}], //强制使用一致的缩进
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}], // 强制使用一致的反勾号、双引号或单引号
'no-mixed-spaces-and-tabs': 2, //禁止空格和 tab 的混合缩进
'jsx-quotes': [2, 'prefer-single'], //强制在 JSX 属性中一致地使用双引号或单引号
'comma-dangle': [2, 'never'], //要求或禁止末尾逗号
'no-dupe-keys': 2, //禁止对象字面量中出现重复的 key
'no-eval': 2, //禁用 eval()
'no-implied-eval': 2, //禁止使用类似 eval() 的方法
'no-with': 2, //禁用 with 语句
'no-redeclare': 2, //禁止多次声明同一变量
'no-undef': 2, //禁用未声明的变量,除非它们在 /*global */ 注释中被提到
'no-undef-init': 2, //禁止将变量初始化为 undefined
'prefer-const': 2, //要求使用 const 声明那些声明后不再被修改的变量
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, //禁用 debugger
'no-console': process.env.NODE_ENV === 'production' ? 2 : 0, //禁用 console
//undetermined
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}], //强制每行属性的最大数量
"vue/singleline-html-element-content-newline": 0, //需要在单行元素的内容前后换行
"vue/multiline-html-element-content-newline": 0, //需要在多行元素的内容前后换行
"vue/name-property-casing": [2, "PascalCase"], //为Vue组件中的name属性强制特定的大小写(下划线)
'accessor-pairs': 2, //强制 getter 和 setter 在对象中成对出现
'arrow-spacing': [1, {
'before': true,
'after': true
}], //强制箭头函数的箭头前后使用一致的空格
'block-spacing': [2, 'always'], //禁止或强制在代码块中开括号前和闭括号后有空格
'brace-style': [2, '1tbs', {
'allowSingleLine': true //(默认 false) 允许块的开括号和闭括号在 同一行
}], //强制在代码块中使用一致的大括号风格
'camelcase': [0, {
'properties': 'always' //(默认) 强制属性名称为驼峰风格
}], //强制使用骆驼拼写法命名约定
'comma-spacing': [2, {
'before': false,
'after': true
}], //强制在逗号前后使用一致的空格
'comma-style': [2, 'last'], //强制使用一致的逗号风格
'constructor-super': 2, //要求在构造函数中有 super() 的调用
'curly': [2, 'multi-line'], //强制所有控制语句使用一致的括号风格
'dot-location': [2, 'property'], //强制在点号之前和之后一致的换行
'eol-last': 2, //要求或禁止文件末尾存在空行
'generator-star-spacing': [2, {
'before': true,
'after': true
}], //强制 generator 函数中 * 号周围使用一致的空格
'handle-callback-err': [0, '^(err|error)$'], //要求回调函数中有容错处理
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}], //强制在对象字面量的属性中键和值之间使用一致的间距
'keyword-spacing': [2, {
'before': true,
'after': true
}], //强制在关键字前后使用一致的空格
'new-cap': [2, {
'newIsCap': true, //(默认true) 要求调用 new 操作符时有首字母大小的函数
'capIsNew': false //允许调用首字母大写的函数时没有 new 操作符
}], //要求构造函数首字母大写
'new-parens': 2, //要求调用无参构造函数时有圆括号
'no-array-constructor': 2, //禁用 Array 构造函数
'no-caller': 2, //禁用 arguments.caller 或 arguments.callee
'no-class-assign': 2, //禁止修改类声明的变量
'no-cond-assign': 0, //禁止条件表达式中出现赋值操作符
'no-const-assign': 2, //禁止修改 const 声明的变量
'no-control-regex': 2, //禁止在正则表达式中使用控制字符
'no-delete-var': 2, //禁止删除变量
'no-dupe-args': 2, //禁止 function 定义中出现重名参数
'no-dupe-class-members': 2, //禁止类成员中出现重复的名称
'no-duplicate-case': 2, //禁止出现重复的 case 标签
'no-empty-character-class': 2, //禁止在正则表达式中使用空字符集
'no-empty-pattern': 2, //禁止使用空解构模式
'no-ex-assign': 2, //禁止对 catch 子句的参数重新赋值
'no-extend-native': 2, //禁止扩展原生类型
'no-extra-bind': 2, //禁止不必要的 .bind() 调用
'no-extra-boolean-cast': 2, //禁止不必要的布尔转换
'no-extra-parens': [2, 'functions'], //禁止不必要的括号
'no-fallthrough': 2, //禁止 case 语句落空
'no-floating-decimal': 2, //禁止数字字面量中使用前导和末尾小数点
'no-func-assign': 2, //禁止对 function 声明重新赋值
'no-inner-declarations': [2, 'functions'], //禁止在嵌套的块中出现变量声明或 function 声明
'no-invalid-regexp': 2, //禁止 RegExp 构造函数中存在无效的正则表达式字符串
'no-irregular-whitespace': 2, //禁止在字符串和注释之外不规则的空白
'no-iterator': 2, //禁用 __iterator__ 属性
'no-label-var': 2, //不允许标签与变量同名
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}], //禁用标签语句
'no-lone-blocks': 2, //禁用不必要的嵌套块
'no-multi-spaces': 2, //禁止使用多个空格
'no-multi-str': 2, //禁止使用多行字符串
'no-multiple-empty-lines': [2, {
'max': 1
}], //禁止出现多行空行
'no-global-assign': 2, //禁止对原生对象或只读的全局对象进行赋值
'no-unsafe-negation': 2, //禁止对关系运算符的左操作数使用否定操作符
'no-new-object': 2, // 禁用 Object 的构造函数
'no-new-require': 2, //禁止调用 require 时使用 new 操作符
'no-new-symbol': 2, //禁止 Symbolnew 操作符和 new 一起使用
'no-new-wrappers': 2, //禁止对 String,Number 和 Boolean 使用 new 操作符
'no-obj-calls': 2, //禁止把全局对象作为函数调用
'no-octal': 2, //禁用八进制字面量
'no-octal-escape': 2, //禁止在字符串中使用八进制转义序列
'no-path-concat': 2, //禁止对 __dirname 和 __filename 进行字符串连接
'no-proto': 2, //禁用 __proto__ 属性
'no-regex-spaces': 2, //禁止正则表达式字面量中出现多个空格
'no-return-assign': [2, 'except-parens'], //禁止在 return 语句中使用赋值语句
'no-self-assign': 2, //禁止自我赋值
'no-self-compare': 2, //禁止自身比较
'no-sequences': 2, //禁用逗号操作符
'no-shadow-restricted-names': 2, //禁止将标识符定义为受限的名字
'func-call-spacing': 2, //要求或禁止在函数标识符和其调用之间有空格
'no-sparse-arrays': 2, //禁用稀疏数组
'no-this-before-super': 2, //禁止在构造函数中,在调用 super() 之前使用 this 或 super
'no-throw-literal': 2, //禁止抛出异常字面量
'no-trailing-spaces': 2, //禁用行尾空格
'no-unexpected-multiline': 2, //禁止出现令人困惑的多行表达式
'no-unmodified-loop-condition': 2, //禁用一成不变的循环条件
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}], //禁止可以在有更简单的可替代的表达式时使用三元操作符
'no-unreachable': 2, //禁止在return、throw、continue 和 break 语句之后出现不可达代码
'no-unsafe-finally': 2, // 禁止在 finally 语句块中出现控制流语句
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}], //禁止出现未使用过的变量
'no-useless-call': 2, //禁止不必要的 .call() 和 .apply()
'no-useless-computed-key': 2, //禁止在对象中使用不必要的计算属性
'no-useless-constructor': 2, //禁用不必要的构造函数
'no-useless-escape': 0, //禁用不必要的转义字符
'no-whitespace-before-property': 2, //禁止属性前有空白
'one-var': [2, {
'initialized': 'never'
}], //强制函数中的变量要么一起声明要么分开声明
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}], //强制操作符使用一致的换行符
'padded-blocks': [2, 'never'], //要求或禁止块内填充
'semi': [2, 'always'], //要求或禁止使用分号代替 ASI
'semi-spacing': [2, {
'before': false,
'after': true
}], //强制分号之前和之后使用一致的空格
'space-before-blocks': [2, 'always'], //强制在块之前使用一致的空格
'space-before-function-paren': [2, 'never'], //强制在 function的左括号之前使用一致的空格
'space-in-parens': [2, 'never'], //强制在圆括号内使用一致的空格
'space-infix-ops': 2, //要求操作符周围有空格
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}], //强制在一元操作符前后使用一致的空格
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}], //强制在注释中 // 或 /* 使用一致的空格
'template-curly-spacing': [2, 'never'], //要求或禁止模板字符串中的嵌入表达式周围空格的使用
'use-isnan': 2, //要求使用 isNaN() 检查 NaN
'valid-typeof': 2, //强制 typeof 表达式与有效的字符串进行比较
'wrap-iife': [2, 'any'], //要求 IIFE 使用括号括起来
'yield-star-spacing': [2, 'both'], //强制在 yield* 表达式中 * 周围使用空格
'yoda': [2, 'never'], //要求或禁止 “Yoda” 条件
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}], //强制在大括号中使用一致的空格
'array-bracket-spacing': [2, 'never'] //强制数组方括号中使用一致的空格
}
}
css校验
stylelint
安装
npm install --save-dev stylelint stylelint-config-standard
校验
npx stylelint "**/*.css"
与postcss 结合使用
const fs = require("fs");
const less = require("postcss-less");
const postcss = require("postcss");
// Code to be processed
const code = fs.readFileSync("input.less", "utf8");
postcss([
require("stylelint")({
/* your options */
}),
require("postcss-reporter")({ clearReportedMessages: true })
])
.process(code, {
from: "input.less",
syntax: less
})
.then(() => {})
.catch((err) => console.error(err.stack));
参考资料:
.stylelintrc 文件
{
"extends": "stylelint-config-standard",
"plugins": ["stylelint-order"],
"rules": {
"order/order": [
"declarations",
"custom-properties",
"dollar-variables",
"rules",
"at-rules"
],
"order/properties-order": [
"position",
"z-index",
"top",
"bottom",
"left",
"right",
"float",
"clear",
"columns",
"columns-width",
"columns-count",
"column-rule",
"column-rule-width",
"column-rule-style",
"column-rule-color",
"column-fill",
"column-span",
"column-gap",
"display",
"grid",
"grid-template-rows",
"grid-template-columns",
"grid-template-areas",
"grid-auto-rows",
"grid-auto-columns",
"grid-auto-flow",
"grid-column-gap",
"grid-row-gap",
"grid-template",
"grid-template-rows",
"grid-template-columns",
"grid-template-areas",
"grid-gap",
"grid-row-gap",
"grid-column-gap",
"grid-area",
"grid-row-start",
"grid-row-end",
"grid-column-start",
"grid-column-end",
"grid-column",
"grid-column-start",
"grid-column-end",
"grid-row",
"grid-row-start",
"grid-row-end",
"flex",
"flex-grow",
"flex-shrink",
"flex-basis",
"flex-flow",
"flex-direction",
"flex-wrap",
"justify-content",
"align-content",
"align-items",
"align-self",
"order",
"table-layout",
"empty-cells",
"caption-side",
"border-collapse",
"border-spacing",
"list-style",
"list-style-type",
"list-style-position",
"list-style-image",
"ruby-align",
"ruby-merge",
"ruby-position",
"box-sizing",
"width",
"min-width",
"max-width",
"height",
"min-height",
"max-height",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"border",
"border-width",
"border-top-width",
"border-right-width",
"border-bottom-width",
"border-left-width",
"border-style",
"border-top-style",
"border-right-style",
"border-bottom-style",
"border-left-style",
"border-color",
"border-top-color",
"border-right-color",
"border-bottom-color",
"border-left-color",
"border-image",
"border-image-source",
"border-image-slice",
"border-image-width",
"border-image-outset",
"border-image-repeat",
"border-top",
"border-top-width",
"border-top-style",
"border-top-color",
"border-top",
"border-right-width",
"border-right-style",
"border-right-color",
"border-bottom",
"border-bottom-width",
"border-bottom-style",
"border-bottom-color",
"border-left",
"border-left-width",
"border-left-style",
"border-left-color",
"border-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"border-top-left-radius",
"outline",
"outline-width",
"outline-color",
"outline-style",
"outline-offset",
"overflow",
"overflow-x",
"overflow-y",
"resize",
"visibility",
"font",
"font-style",
"font-variant",
"font-weight",
"font-stretch",
"font-size",
"font-family",
"font-synthesis",
"font-size-adjust",
"font-kerning",
"line-height",
"text-align",
"text-align-last",
"vertical-align",
"text-overflow",
"text-justify",
"text-transform",
"text-indent",
"text-emphasis",
"text-emphasis-style",
"text-emphasis-color",
"text-emphasis-position",
"text-decoration",
"text-decoration-color",
"text-decoration-style",
"text-decoration-line",
"text-underline-position",
"text-shadow",
"white-space",
"overflow-wrap",
"word-wrap",
"word-break",
"line-break",
"hyphens",
"letter-spacing",
"word-spacing",
"quotes",
"tab-size",
"orphans",
"writing-mode",
"text-combine-upright",
"unicode-bidi",
"text-orientation",
"direction",
"text-rendering",
"font-feature-settings",
"font-language-override",
"image-rendering",
"image-orientation",
"image-resolution",
"shape-image-threshold",
"shape-outside",
"shape-margin",
"color",
"background",
"background-image",
"background-position",
"background-size",
"background-repeat",
"background-origin",
"background-clip",
"background-attachment",
"background-color",
"background-blend-mode",
"isolation",
"clip-path",
"mask",
"mask-image",
"mask-mode",
"mask-position",
"mask-size",
"mask-repeat",
"mask-origin",
"mask-clip",
"mask-composite",
"mask-type",
"filter",
"box-shadow",
"opacity",
"transform-style",
"transform",
"transform-box",
"transform-origin",
"perspective",
"perspective-origin",
"backface-visibility",
"transition",
"transition-property",
"transition-duration",
"transition-timing-function",
"transition-delay",
"animation",
"animation-name",
"animation-duration",
"animation-timing-function",
"animation-delay",
"animation-iteration-count",
"animation-direction",
"animation-fill-mode",
"animation-play-state",
"scroll-behavior",
"scroll-snap-type",
"scroll-snap-destination",
"scroll-snap-coordinate",
"cursor",
"touch-action",
"caret-color",
"ime-mode",
"object-fit",
"object-position",
"content",
"counter-reset",
"counter-increment",
"will-change",
"pointer-events",
"all",
"page-break-before",
"page-break-after",
"page-break-inside",
"widows"
],
"no-empty-source": null,
"property-no-vendor-prefix": [true, {"ignoreProperties": ["background-clip", "box-orient"]}],
"number-leading-zero": "never",
"number-no-trailing-zeros": true,
"length-zero-no-unit": true,
"property-no-unknown": [true, {"ignoreProperties": ["box-orient"]}],
"value-list-comma-space-after": "always",
"declaration-colon-space-after": "always",
"value-list-max-empty-lines": 0,
"shorthand-property-no-redundant-values": true,
"declaration-block-no-duplicate-properties": true,
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-semicolon-newline-after": "always",
"block-closing-brace-newline-after": "always",
"media-feature-colon-space-after": "always",
"media-feature-range-operator-space-after": "always",
"at-rule-name-space-after": "always",
"indentation": 4,
"no-eol-whitespace": true,
"string-no-newline": null
}
}
支持vue编译
const { VueLoaderPlugin } = require('vue-loader');
{
test: /\.vue$/,
use: [
'cache-loader',
{
loader: 'vue-loader',
options: {
// other vue-loader options go here
}
}
]
}
plugins:[new VueLoaderPlugin()]
配置ts开发环
使用ts-loader
使用ts需要安装ts相关配置
npm install typescript ts-loader --save-dev
生成ts的配置文件
npx tsc --init
配置ts-loader
{
test:/\.tsx?/,
use: ['ts-loader'],
exclude: /node_modules/
}
将入口文件更改成ts文件
let a:string = 'hello';
console.log(a);
执行npm run dev发现已经可以正常的解析ts文件啦!
使用 preset-typescript
不需要借助typescript
npm install @babel/preset-typescript
{
"presets": [
["@babel/preset-env",{
"useBuiltIns":"usage",
"corejs":2
}],
"@babel/preset-react",
["@babel/preset-typescript",{
"allExtensions": true
}]
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties",{"loose":true}],
"@babel/plugin-transform-runtime"
]
}
配置ts+react环境
安装react相关模块
npm i @babel/preset-react --save-dev # 解析jsx语法
npm i react @types/react @types/react-dom react react-dom typescript
import React from 'react';
import ReactDOM from 'react-dom';
const state = {number:0};
type State = Readonly<typeof state>;
class Counter extends React.Component<object,State>{
state:State = state
handleClick =()=>{
this.setState({number:this.state.number+1})
}
render(){
const {number} = this.state;
return (
<div>
<button onClick={this.handleClick}>点击</button>
{number}
</div>
)
}
}
ReactDOM.render(<Counter></Counter>,document.getElementById('root'));
配置ts+vue环境
安装vue所需要的模块
npm install vue-loader vue-template-compiler --save-dev
npm install vue vue-property-decorator
配置ts-loader
{
test: /\.tsx?/,
use: {
loader:'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
},
},
exclude: /node_modules/
}
使用vue-loader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin');
new VueLoaderPlugin();
配置解析.vue文件
{
test:/\.vue$/,
use:'vue-loader'
}
增加vue-shims.d.ts,可以识别.vue文件
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
index.tsx文件
import Vue from 'vue';
import App from './App.vue';
let vm = new Vue({
render:h=>h(App)
}).$mount('#root')
App.vue文件
<template>
<div>
<div v-for="(todo,index) in todos" :key="index">{{todo}}</div>
</div>
</template>
<script lang="ts">
import {Component,Vue} from 'vue-property-decorator';
@Component
export default class Todo extends Vue{
public todos = ['香蕉','苹果','橘子']
}
</script>
常见plugin
const CopyWebpackPlugin = require('copy-webpack-plugin'); // 拷贝
const HappyPack = require('happypack'); // 多进程运行
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');// css压缩
const TerserPlugin = require('terser-webpack-plugin');
const happyThreadPool = HappyPack.ThreadPool({
size: os.cpus().length
});
rules:[
{
test: /\.(j|t)sx?$/,
use: 'happypack/loader?id=happy-babel-js' // 增加新的HappyPack构建loader
}
],
plugins: [
new CopyWebpackPlugin([
{
from: './src/common',
to: './common'
}
]),
new HappyPack({
id: 'happy-babel-js',
loaders: [
'cache-loader',
{
loader: 'babel-loader'
}
],
threadPool: happyThreadPool
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
aaa: JSON.stringify({
version: 'version',
})
}),
new OptimizeCssAssetsPlugin(),
new webpack.HashedModuleIdsPlugin({
hashDigest: 'hex'
}),
new webpack.NamedChunksPlugin((chunk) => {
if (chunk.name) {
return chunk.name;
}
const hash = require('hash-sum');
const joinedHash = hash(
Array.from(chunk.modulesIterable, (m) => m.id).join('_')
);
return `chunk-` + joinedHash;
})
],
optimization: {
minimize: true,
minimizer: [new TerserPlugin({
extractComments: false,
terserOptions: {
output: {
comments: false
},
cache: true, // 开启缓存
parallel: true, // 开启多线程压缩
compress: { // 配置
// 是有有必要开启
warnings: true, // 关闭警告:删除无法访问的代码或未使用的声明等时显示警告
drop_console: true,
drop_debugger: true
}
}
})]
}
source map
- sourcemap是为了解决开发代码与实际运行代码不一致时帮助我们debug到原始开发代码的技术
- webpack通过配置可以自动给我们
source maps文件,map文件是一种对应编译文件和源文件的方法 - whyeval
- source-map
- javascript_source_map算法 | 类型 | 含义 | | --- | --- | | source-map | 原始代码 最好的sourcemap质量有完整的结果,但是会很慢 | | eval-source-map | 原始代码 同样道理,但是最高的质量和最低的性能 | | cheap-module-eval-source-map | 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能 | | cheap-eval-source-map | 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl | | eval | 生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap | | cheap-source-map | 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用 | | cheap-module-source-map | 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射 |
看似配置项很多, 其实只是五个关键字eval、source-map、cheap、module和inline的任意组合
| 关键字 | 含义 |
|---|---|
| eval | 使用eval包裹模块代码 |
| source-map | 产生.map文件 |
| cheap | 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap |
| module | 包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件 |
| inline | 将.map作为DataURI嵌入,不单独生成.map文件 |
- eval eval执行
- eval-source-map 生成sourcemap
- cheap-module-eval-source-map 不包含列
- cheap-eval-source-map 无法看到真正的源码
devtool:"cheap-module-eval-source-map",// 开发环境配置
参考
参考文档
常用loader列表
- webpack 可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。你可以使用 Node.js 来很简单地编写自己的 loader。
- awesome-loaders
文件
- raw-loader 加载文件原始内容(utf-8)
- val-loader 将代码作为模块执行,并将 exports 转为 JS 代码
- url-loader 像 file loader 一样工作,但如果文件小于限制,可以返回 data URL
- file-loader 将文件发送到输出文件夹,并返回(相对)URL
JSON
- json-loader 加载 JSON 文件(默认包含)
- json5-loader 加载和转译 JSON 5 文件
- cson-loader 加载和转译 CSON 文件
转换编译(Transpiling)
- script-loader 在全局上下文中执行一次 JavaScript 文件(如在 script 标签),不需要解析
- babel-loader 加载 ES2015+ 代码,然后使用 Babel 转译为 ES5
- buble-loader 使用 Bublé 加载 ES2015+ 代码,并且将代码转译为 ES5
- traceur-loader 加载 ES2015+ 代码,然后使用 Traceur 转译为 ES5
- ts-loader 或 awesome-typescript-loader 像 JavaScript 一样加载 TypeScript 2.0+
- coffee-loader 像 JavaScript 一样加载 CoffeeScript
模板(Templating)
- html-loader 导出 HTML 为字符串,需要引用静态资源
- pug-loader 加载 Pug 模板并返回一个函数
- jade-loader 加载 Jade 模板并返回一个函数
- markdown-loader 将 Markdown 转译为 HTML
- react-markdown-loader 使用 markdown-parse parser(解析器) 将 Markdown 编译为 React 组件
- posthtml-loader 使用 PostHTML 加载并转换 HTML 文件
- handlebars-loader 将 Handlebars 转移为 HTML
- markup-inline-loader 将内联的 SVG/MathML 文件转换为 HTML。在应用于图标字体,或将 CSS 动画应用于 SVG 时非常有用
样式
- style-loader 将模块的导出作为样式添加到 DOM 中
- css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
- less-loader 加载和转译 LESS 文件
- sass-loader 加载和转译 SASS/SCSS 文件
- postcss-loader 使用 PostCSS 加载和转译 CSS/SSS 文件
- stylus-loader 加载和转译 Stylus 文件
清理和测试(Linting && Testing)
- mocha-loader 使用 mocha 测试(浏览器/NodeJS)
- eslint-loader PreLoader,使用 ESLint 清理代码
- jshint-loader PreLoader,使用 JSHint 清理代码
- jscs-loader PreLoader,使用 JSCS 检查代码样式
- coverjs-loader PreLoader,使用 CoverJS 确定测试覆盖率
框架(Frameworks)
- vue-loader 加载和转译 Vue 组件
- polymer-loader 使用选择预处理器(preprocessor)处理,并且 require() 类似一等模块(first-class)的 Web 组件
- angular2-template-loader 加载和转译 Angular 组件