webpack 基础配置总结

617 阅读18分钟

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-loaderwebpackloader的桥梁。

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 的编译代码 image.png

配置transform-runtime 之后的编译代码 image.png

我们发现 transform-runtime 把我们写的代码都改变了。进行了重新包装,这样子就不会有全局污染了。

比较

体积压缩后体积结论
使用全量引入,@babel/polyfill470KiB87.2 KiB最好不要使用
按需引入114 KiB19.8 KiB在项目开发中使用比较合适
transform-runtime157 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));

参考资料:

stylelint postcss插件

.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 组件